diff --git a/.envrc b/.envrc index fb0a886d24..8ac5b114e3 100644 --- a/.envrc +++ b/.envrc @@ -25,7 +25,7 @@ export VITE_BACKEND_HTTP_PORT='55002' export VITE_BACKEND_GRPC_PORT="56002" export VITE_BACKEND_P2P_PORT="56003" -export NEXT_PUBLIC_GRPC_HOST="https://gateway.mintter.com" +export NEXT_PUBLIC_GRPC_HOST="http://localhost:57001" # export NEXT_PUBLIC_GRPC_HOST="http://localhost:56001" export NEXT_PUBLIC_LN_HOST="https://ln.mintter.com" export MINTTER_IS_GATEWAY="1" diff --git a/.github/workflows/generate-docker-images.yml b/.github/workflows/generate-docker-images.yml index bb5f64bb59..5ed56e8caa 100644 --- a/.github/workflows/generate-docker-images.yml +++ b/.github/workflows/generate-docker-images.yml @@ -23,6 +23,12 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push mintter-site + uses: docker/build-push-action@v4 + with: + push: true + file: backend/cmd/mintter-site/Dockerfile + tags: mintter/mintter-site:latest - name: Build and push mintterd uses: docker/build-push-action@v4 with: diff --git a/.golangci.yaml b/.golangci.yaml index 4eac5000cd..daf2482c90 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -12,17 +12,21 @@ issues: exclude-rules: # Unused parameter warnings are annoying in tests. - path: _test\.go - linters: ['revive'] - text: 'unused-parameter' + linters: ["revive"] + text: "unused-parameter" # For test helpers it's often convenient to pass *testing.T as the first parameter. # We don't care if context is not the first argument here. - path: _test\.go - text: 'context-as-argument' - linters: ['revive'] + text: "context-as-argument" + linters: ["revive"] # In tests we can use math/rand instead of crypto/rand. - path: _test\.go - text: 'weak random number generator' - linters: ['gosec'] + text: "weak random number generator" + linters: ["gosec"] + # Get rid of the annoying "if-return" rule. + # It gets in the way as soon as you decide to wrap the last error instead of returning it. + - text: "if-return" + linters: ["revive"] include: # Include revive rules that check comments on exported identifiers. These rules # are excluded by default, and IMHO this doesn't serve well the Go community. diff --git a/backend/BUILD.plz b/backend/BUILD.plz index 0f15a81d34..a6fe5ab9dd 100644 --- a/backend/BUILD.plz +++ b/backend/BUILD.plz @@ -8,10 +8,10 @@ go_binary( ["**/*.go"], exclude = ["**/*_test.go"], ) + [ + "//backend/cmd/mintter-site/sitesql:go_library", "//backend/daemon/storage:go_library", "//backend/hyper/hypersql:go_library", "//backend/lndhub/lndhubsql:go_library", - "//backend/mttnet/sitesql:go_library", "//backend/wallet/walletsql:go_library", ], out = "mintterd-" + target_platform_triple(), diff --git a/backend/cmd/minttergw/Dockerfile b/backend/cmd/mintter-site/Dockerfile similarity index 61% rename from backend/cmd/minttergw/Dockerfile rename to backend/cmd/mintter-site/Dockerfile index 9e67a1c898..5cad8d79e5 100644 --- a/backend/cmd/minttergw/Dockerfile +++ b/backend/cmd/mintter-site/Dockerfile @@ -1,4 +1,4 @@ -# Build from the root with `docker build . -f ./backend/cmd/minttergw/Dockerfile`. +# Build from the root with `docker build . -f ./backend/cmd/mintter-site/Dockerfile`. FROM golang:1.20-alpine AS builder WORKDIR /code COPY go.mod go.sum ./ @@ -6,9 +6,9 @@ COPY third_party ./third_party RUN go mod download COPY backend ./backend RUN apk add build-base -RUN go install ./backend/cmd/minttergw/ +RUN go install ./backend/cmd/mintter-site/ FROM alpine:latest -COPY --from=builder /go/bin/minttergw /usr/local/bin/minttergw +COPY --from=builder /go/bin/mintter-site /usr/local/bin/mintter-site EXPOSE 55000 55001 55002 -CMD ["/usr/local/bin/minttergw"] +CMD ["/usr/local/bin/mintter-site"] diff --git a/backend/cmd/mintter-site/main.go b/backend/cmd/mintter-site/main.go new file mode 100644 index 0000000000..bab22e7e75 --- /dev/null +++ b/backend/cmd/mintter-site/main.go @@ -0,0 +1,77 @@ +// Program mintter-site implements the Hypermedia Site server. +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "os" + + "mintter/backend/cmd/mintter-site/sites" + "mintter/backend/daemon" + + "github.com/burdiyan/go/mainutil" + "github.com/peterbourgon/ff/v3" +) + +func main() { + const envVarPrefix = "MINTTER" + + mainutil.Run(func() error { + ctx := mainutil.TrapSignals() + + fs := flag.NewFlagSet("mintter-site", flag.ExitOnError) + fs.Usage = func() { + fmt.Fprintf(fs.Output(), `Usage: %s ADDRESS [flags] + +This program is similar to our main mintterd program in a lot of ways, but has more suitable defaults for running on a server as site. + +It requires one positional argument ADDRESS, which has to be a Web network address this site is supposed to be available at. +The address can be a DNS name, or an IP address, and it has to be a URL with a scheme and port (if applicable). +Examples: + - http://127.0.0.1:42542 + - https://mintter.com + - http://example.com + +Flags: +`, fs.Name()) + fs.PrintDefaults() + } + + cfg := sites.DefaultConfig() + cfg.BindFlags(fs) + if err := ff.Parse(fs, os.Args[1:], ff.WithEnvVarPrefix(envVarPrefix)); err != nil { + return err + } + + if len(os.Args) < 2 { + fs.Usage() + fmt.Fprintf(fs.Output(), "Error: Positional argument ADDRESS is missing.\n") + os.Exit(1) + } + + rawURL := os.Args[1] + + if err := cfg.Base.ExpandDataDir(); err != nil { + return err + } + + dir, err := daemon.InitRepo(cfg.Base.DataDir, nil) + if err != nil { + return err + } + + app, err := sites.Load(ctx, rawURL, cfg, dir) + if err != nil { + return err + } + + err = app.Wait() + if errors.Is(err, context.Canceled) { + return nil + } + + return err + }) +} diff --git a/backend/cmd/mintter-site/sites/config.go b/backend/cmd/mintter-site/sites/config.go new file mode 100644 index 0000000000..3bac85bd53 --- /dev/null +++ b/backend/cmd/mintter-site/sites/config.go @@ -0,0 +1,14 @@ +package sites + +import "mintter/backend/config" + +// DefaultConfig for sites. +func DefaultConfig() config.Config { + cfg := config.Default() + cfg.DataDir = "~/.mintter-site" + cfg.Syncing.Disabled = true + cfg.P2P.ForceReachabilityPublic = true + cfg.P2P.NoRelay = true + + return cfg +} diff --git a/backend/cmd/mintter-site/sites/daemon.go b/backend/cmd/mintter-site/sites/daemon.go new file mode 100644 index 0000000000..f66e037312 --- /dev/null +++ b/backend/cmd/mintter-site/sites/daemon.go @@ -0,0 +1,106 @@ +package sites + +import ( + "context" + "errors" + "fmt" + "mintter/backend/config" + "mintter/backend/core" + "mintter/backend/daemon" + "mintter/backend/daemon/storage" + accounts "mintter/backend/genproto/accounts/v1alpha" + "mintter/backend/ipfs" + "mintter/backend/mttnet" + "mintter/backend/pkg/future" + "mintter/backend/pkg/must" + "net/url" + + "crawshaw.io/sqlite/sqlitex" +) + +// App is the site daemon app. +type App struct { + *daemon.App + + Website *Website + Address *url.URL + Config config.Config +} + +// Load the site daemon. +func Load(ctx context.Context, address string, cfg config.Config, dir *storage.Dir) (*App, error) { + u, err := url.Parse(address) + if err != nil { + return nil, fmt.Errorf("failed to parse address: %w", err) + } + + if u.Scheme != "http" && u.Scheme != "https" { + return nil, fmt.Errorf("address URL only supports http or https: got %q", address) + } + + if u.Path != "" { + return nil, fmt.Errorf("address URL must not have a path: %s", address) + } + + cfg.P2P.AnnounceAddrs = must.Do2( + ipfs.ParseMultiaddrs( + ipfs.DefaultListenAddrsDNS(u.Hostname(), cfg.P2P.Port))) + + nf := future.New[*mttnet.Node]() + ndb := future.New[*sqlitex.Pool]() + site := NewServer(address, nf.ReadOnly, ndb.ReadOnly) + + app, err := daemon.Load(ctx, cfg, dir, site, daemon.GenericHandler{ + Path: "/.well-known/hypermedia-site", + Handler: site, + Mode: daemon.RouteNav, + }) + if err != nil { + return nil, err + } + + // This is some ugly stuff. Site server needs some stuff that are passed from the daemon. + go func() { + if err := ndb.Resolve(app.DB); err != nil { + panic(err) + } + + node, err := app.Net.Await(ctx) + if err != nil && !errors.Is(err, context.Canceled) { + panic(err) + } + + if err := nf.Resolve(node); err != nil { + panic(err) + } + }() + + if _, ok := dir.Identity().Get(); !ok { + account, err := core.NewKeyPairRandom() + if err != nil { + return nil, fmt.Errorf("failed to generate random account key pair: %w", err) + } + + if err := app.RPC.Daemon.RegisterAccount(ctx, account); err != nil { + return nil, fmt.Errorf("failed to create registration: %w", err) + } + } + + if _, err := app.RPC.Accounts.UpdateProfile(ctx, &accounts.Profile{ + Alias: address, + Bio: "Hypermedia Site. Powered by Mintter.", + }); err != nil { + return nil, fmt.Errorf("failed to update profile: %w", err) + } + + setupURL := site.GetSetupURL(ctx) + + fmt.Println("Site Invitation secret token: " + setupURL) + + return &App{ + App: app, + Website: site, + Address: u, + Config: cfg, + }, nil +} diff --git a/backend/cmd/mintter-site/sites/sites.go b/backend/cmd/mintter-site/sites/sites.go new file mode 100644 index 0000000000..0793c36ce0 --- /dev/null +++ b/backend/cmd/mintter-site/sites/sites.go @@ -0,0 +1,351 @@ +// Package sites implements mintter-site server. +package sites + +import ( + "context" + "crypto/sha256" + "encoding/base64" + "errors" + "fmt" + "mintter/backend/cmd/mintter-site/sitesql" + "mintter/backend/daemon/storage" + groups "mintter/backend/genproto/groups/v1alpha" + "mintter/backend/hyper" + "mintter/backend/hyper/hypersql" + "mintter/backend/mttnet" + "mintter/backend/pkg/future" + "net/http" + "sync" + + "google.golang.org/grpc/codes" + rpcpeer "google.golang.org/grpc/peer" + "google.golang.org/grpc/status" + + "crawshaw.io/sqlite/sqlitex" + "github.com/ipfs/go-cid" + "github.com/libp2p/go-libp2p/core/peer" + "google.golang.org/protobuf/encoding/protojson" +) + +// Website is the gate to manipulate internal node structures +type Website struct { + // Network of the node. + node *future.ReadOnly[*mttnet.Node] + // db access to the node. + db *future.ReadOnly[*sqlitex.Pool] + // url is the protocol + hostname the group is being served at. + url string + + once sync.Once + setupSecret string +} + +var errNodeNotReadyYet = errors.New("P2P node is not ready yet") + +// NewServer creates a new server for the site. +func NewServer(url string, n *future.ReadOnly[*mttnet.Node], db *future.ReadOnly[*sqlitex.Pool]) *Website { + return &Website{ + node: n, + db: db, + url: url, + } +} +func (ws *Website) ServeHTTP(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") + w.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET") + siteInfo, err := ws.GetSiteInfo(context.Background(), &groups.GetSiteInfoRequest{}) + + if err != nil { + if errors.Is(err, errNodeNotReadyYet) { + w.Header().Set("Retry-After", "30") + http.Error(w, err.Error(), http.StatusServiceUnavailable) + } else { + http.Error(w, "Could not get site info: "+err.Error(), http.StatusInternalServerError) + } + return + } + + data, err := protojson.MarshalOptions{Indent: " ", EmitUnpopulated: true}.Marshal(siteInfo) + if err != nil { + http.Error(w, "Failed to marshal site info: "+err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, err = w.Write(data) + if err != nil { + return + } +} + +// GetSetupURL returns the secret URL to setup this site. +// We want to make the secret URL deterministic so that it persists across restarts. +// We could use a random string, but that would require persisting it in the database, +// which is not a problem, but seems unnecessary. +func (ws *Website) GetSetupURL(ctx context.Context) string { + return ws.url + "/secret-invite/" + ws.getSetupSecret(ctx) +} + +func (ws *Website) getSetupSecret(ctx context.Context) string { + ws.once.Do(func() { + node, err := ws.node.Await(ctx) + if err != nil { + panic(err) + } + + signature, err := node.ID().DeviceKey().Sign([]byte("hypermedia-site-setup-secret")) + if err != nil { + panic(err) + } + + sum := sha256.Sum256(signature) + + ws.setupSecret = base64.RawURLEncoding.EncodeToString(sum[:16]) + }) + + return ws.setupSecret +} + +// GetSiteInfo exposes the public information of a site. Which group is serving and how to reach the site via p2p. +func (ws *Website) GetSiteInfo(ctx context.Context, in *groups.GetSiteInfoRequest) (*groups.PublicSiteInfo, error) { + n, ok := ws.node.Get() + if !ok { + return nil, errNodeNotReadyYet + } + + db, err := ws.db.Await(ctx) + if err != nil { + return nil, err + } + + conn, release, err := db.Conn(ctx) + if err != nil { + return nil, fmt.Errorf("Failed to get db connection: %w", err) + } + defer release() + + gid, err := sitesql.GetServedGroupID(conn) + if err != nil { + return nil, fmt.Errorf("Failed to get group id from the db: %w", err) + } + + resp := &groups.PublicSiteInfo{ + PeerInfo: &groups.PeerInfo{}, + GroupId: gid.KVValue, + } + + for _, address := range n.AddrInfo().Addrs { + resp.PeerInfo.Addrs = append(resp.PeerInfo.Addrs, address.String()) + } + resp.PeerInfo.PeerId = n.ID().DeviceKey().PeerID().String() + resp.PeerInfo.AccountId = n.ID().Account().ID().String() + + groupID, err := sitesql.GetServedGroupID(conn) + if err != nil { + return nil, fmt.Errorf("Could not get group ID: %w", err) + } + if groupID.KVValue == "" { + // The site is not initialized yet + return resp, nil + } + + entity, err := n.Blobs().LoadEntity(ctx, hyper.EntityID(groupID.KVValue)) + if err != nil { + return nil, fmt.Errorf("could not get entity [%s]: %w", groupID.KVValue, err) + } + + if entity != nil { + resp.GroupVersion = entity.Version().String() + } + + return resp, nil +} + +// InitializeServer starts serving a group in this site. +func (ws *Website) InitializeServer(ctx context.Context, in *groups.InitializeServerRequest) (*groups.InitializeServerResponse, error) { + n, ok := ws.node.Get() + if !ok { + return nil, errNodeNotReadyYet + } + + gid, err := ws.GetGroupID(ctx) + if err != nil { + return nil, err + } + if gid != "" { + return nil, status.Errorf(codes.FailedPrecondition, "site is already initialized") + } + + link := ws.GetSetupURL(ctx) + if link != in.Secret { + return nil, status.Errorf(codes.PermissionDenied, "provided setup secret is not valid") + } + + remoteDeviceID, err := getRemoteID(ctx) + if err != nil { + return nil, fmt.Errorf("failed to extract peer ID from headers: %w", err) + } + + _, err = n.AccountForDevice(ctx, remoteDeviceID) + if err != nil { + return nil, fmt.Errorf("couldn't get account ID from device [%s]: %w", remoteDeviceID.String(), err) + } + + db, err := ws.db.Await(ctx) + if err != nil { + return nil, err + } + + conn, release, err := db.Conn(ctx) + if err != nil { + return nil, err + } + defer release() + + _, err = hypersql.EntitiesInsertOrIgnore(conn, in.GroupId) + if err != nil { + return nil, err + } + + if err := sitesql.SetServedGroupID(conn, in.GroupId); err != nil { + return nil, err + } + + return &groups.InitializeServerResponse{}, nil +} + +// GetGroupID returns the group ID this site is serving. +// It's empty if the site is not initialized yet. +func (ws *Website) GetGroupID(ctx context.Context) (string, error) { + db, err := ws.db.Await(ctx) + if err != nil { + return "", err + } + + conn, release, err := db.Conn(ctx) + if err != nil { + return "", fmt.Errorf("Failed to get db connection: %w", err) + } + defer release() + + dbgroup, err := sitesql.GetServedGroupID(conn) + if err != nil { + return "", err + } + + return dbgroup.KVValue, nil +} + +// PublishBlobs publish blobs to the website. +func (ws *Website) PublishBlobs(ctx context.Context, in *groups.PublishBlobsRequest) (*groups.PublishBlobsResponse, error) { + if len(in.Blobs) < 1 { + return nil, fmt.Errorf("Please, provide at least 1 blob to publish") + } + + n, ok := ws.node.Get() + if !ok { + return nil, errNodeNotReadyYet + } + + db, err := ws.db.Await(ctx) + if err != nil { + return nil, err + } + conn, release, err := db.Conn(ctx) + if err != nil { + return nil, fmt.Errorf("Failed to get db connection: %w", err) + } + defer release() + + // Get caller identity + info, ok := rpcpeer.FromContext(ctx) + if !ok { + return nil, fmt.Errorf("no peer info in context for grpc") + } + + pid, err := peer.Decode(info.Addr.String()) + if err != nil { + return nil, err + } + + authorAcc, err := n.AccountForDevice(ctx, pid) + if err != nil { + return nil, fmt.Errorf("couldn't get account ID from device [%s]: %w", pid.String(), err) + } + + // Get the owner's view of the list of members. + groupID, err := sitesql.GetServedGroupID(conn) + if err != nil || groupID.KVValue == "" { + return nil, fmt.Errorf("Error getting groupID on the site, is the site initialized?: %w", err) + } + + edb, err := hypersql.LookupEnsure(conn, storage.LookupResource, groupID.KVValue) + if err != nil { + return nil, fmt.Errorf("Could not get group (%s) resource: %w", groupID.KVValue, err) + } + + groupOwner, err := hypersql.ResourceGetOwner(conn, edb) + if err != nil { + return nil, fmt.Errorf("Could not get the owner of the group %s: %w", groupID.KVValue, err) + } + + pkdb, err := hypersql.LookupEnsure(conn, storage.LookupPublicKey, authorAcc) + if err != nil { + return nil, fmt.Errorf("couldn't get member entity for account [%s]: %w", authorAcc.String(), err) + } + + // See if the caller is in the owner's group + role, err := hypersql.GroupGetRole(conn, edb, groupOwner, pkdb) + if err != nil { + return nil, fmt.Errorf("Could not get role of member %s in group %s: %w", authorAcc.String(), groupID.KVValue, err) + } + + if role == int64(groups.Role_ROLE_UNSPECIFIED) { + return nil, status.Errorf(codes.PermissionDenied, "Caller [%s] does not have enough permissions to publish to this site.", authorAcc.String()) + } + + want := []cid.Cid{} + for _, cIDStr := range in.Blobs { + c, err := cid.Parse(cIDStr) + if err != nil { + return nil, fmt.Errorf("Could not parse provided blob [%s]: %w", cIDStr, err) + } + res, err := hypersql.BlobsHave(conn, c.Hash()) + if err != nil { + return nil, fmt.Errorf("Could not verify if we had blob [%s] or not: %w", c.String(), err) + } + if res.Have == 0 { + want = append(want, c) + } + } + ses := n.Bitswap().NewSession(ctx) + blkCh, err := ses.GetBlocks(ctx, want) + if err != nil { + return nil, fmt.Errorf("Could not get bitswap channel: %w", err) + } + for { + blk, ok := <-blkCh + if !ok { + return &groups.PublishBlobsResponse{}, nil + } + if err := n.Blobs().IPFSBlockstore().Put(ctx, blk); err != nil { + return nil, fmt.Errorf("Could not store block %s", blk.Cid().String()) + } + } +} + +// getRemoteID gets the remote peer id if there is an opened p2p connection between them with context ctx. +func getRemoteID(ctx context.Context) (peer.ID, error) { + info, ok := rpcpeer.FromContext(ctx) + if !ok { + return "", fmt.Errorf("BUG: no peer info in context for grpc") + } + + pid, err := peer.Decode(info.Addr.String()) + if err != nil { + return "", err + } + + return pid, nil +} diff --git a/backend/cmd/mintter-site/sites/sites_test.go b/backend/cmd/mintter-site/sites/sites_test.go new file mode 100644 index 0000000000..d1795de458 --- /dev/null +++ b/backend/cmd/mintter-site/sites/sites_test.go @@ -0,0 +1,99 @@ +package sites + +import ( + "context" + "errors" + "mintter/backend/config" + "mintter/backend/core/coretest" + "mintter/backend/daemon" + groups "mintter/backend/genproto/groups/v1alpha" + "mintter/backend/ipfs" + "mintter/backend/pkg/must" + "net" + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func TestSiteInit(t *testing.T) { + t.Parallel() + + site := makeTestSite(t, "carol") + ctx := context.Background() + + // Check that we announce our hardcoded web address. + { + wantAnnounceAddrs := must.Do2(ipfs.ParseMultiaddrs(ipfs.DefaultListenAddrsDNS(site.Address.Hostname(), site.Config.P2P.Port))) + require.Equal(t, wantAnnounceAddrs, site.Config.P2P.AnnounceAddrs, "announce addrs don't match") + } + + // Check that our secret setup URL is on the correct domain. + require.True(t, strings.HasPrefix(site.Website.GetSetupURL(ctx), site.Address.String()), "init secret must have a prefix of the announce address") + + alice := daemon.MakeTestApp(t, "alice", daemon.MakeTestConfig(t), true) + + group, err := alice.RPC.Groups.CreateGroup(ctx, &groups.CreateGroupRequest{ + Title: "My test group", + SiteSetupUrl: site.Website.GetSetupURL(ctx), + }) + require.NoError(t, err) + + require.Equal(t, group.Id, must.Do2(site.Website.GetGroupID(ctx)), "site must serve the correct group ID") + + init, err := site.Website.InitializeServer(ctx, &groups.InitializeServerRequest{ + Secret: site.Website.GetSetupURL(ctx), + GroupId: "my-test-group", + }) + require.Nil(t, init) + require.Error(t, err, "subsequent init must fail") + require.Equal(t, codes.FailedPrecondition, status.Code(err), "subsequent init must fail with precondition error") +} + +func makeTestSite(t *testing.T, name string) *App { + ctx, cancel := context.WithCancel(context.Background()) + + user := coretest.NewTester(name) + + cfg := testConfig(t) + dir, err := daemon.InitRepo(cfg.Base.DataDir, user.Device.Wrapped()) + require.NoError(t, err) + + app, err := Load(ctx, "http://127.0.0.1:"+strconv.Itoa(cfg.HTTP.Port), cfg, dir) + require.NoError(t, err) + t.Cleanup(func() { + cancel() + err := app.Wait() + if err != nil { + require.True(t, errors.Is(err, context.Canceled), "unexpected app error: %v", err) + } + }) + + return app +} + +func testConfig(t *testing.T) config.Config { + dir := t.TempDir() + cfg := DefaultConfig() + cfg.Base.DataDir = dir + cfg.HTTP.Port = freePort(t) + cfg.GRPC.Port = 0 + cfg.P2P.Port = freePort(t) + cfg.P2P.BootstrapPeers = nil + cfg.P2P.NoMetrics = true + + return cfg +} + +func freePort(t *testing.T) int { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + require.NoError(t, err) + + l, err := net.ListenTCP("tcp", addr) + require.NoError(t, err) + defer l.Close() + return l.Addr().(*net.TCPAddr).Port +} diff --git a/backend/mttnet/sitesql/BUILD.plz b/backend/cmd/mintter-site/sitesql/BUILD.plz similarity index 100% rename from backend/mttnet/sitesql/BUILD.plz rename to backend/cmd/mintter-site/sitesql/BUILD.plz diff --git a/backend/cmd/mintter-site/sitesql/queries.gen.go b/backend/cmd/mintter-site/sitesql/queries.gen.go new file mode 100644 index 0000000000..025300446b --- /dev/null +++ b/backend/cmd/mintter-site/sitesql/queries.gen.go @@ -0,0 +1,62 @@ +// Code generated by sqlitegen. DO NOT EDIT. + +package sitesql + +import ( + "errors" + "fmt" + + "crawshaw.io/sqlite" + "mintter/backend/pkg/sqlitegen" +) + +var _ = errors.New + +func SetServedGroupID(conn *sqlite.Conn, link string) error { + const query = `INSERT OR REPLACE INTO kv (key, value) +VALUES ('site_group_id', :link)` + + before := func(stmt *sqlite.Stmt) { + stmt.SetText(":link", link) + } + + onStep := func(i int, stmt *sqlite.Stmt) error { + return nil + } + + err := sqlitegen.ExecStmt(conn, query, before, onStep) + if err != nil { + err = fmt.Errorf("failed query: SetServedGroupID: %w", err) + } + + return err +} + +type GetServedGroupIDResult struct { + KVValue string +} + +func GetServedGroupID(conn *sqlite.Conn) (GetServedGroupIDResult, error) { + const query = `SELECT kv.value FROM kv WHERE kv.key ='site_group_id'` + + var out GetServedGroupIDResult + + before := func(stmt *sqlite.Stmt) { + } + + onStep := func(i int, stmt *sqlite.Stmt) error { + if i > 1 { + return errors.New("GetServedGroupID: more than one result return for a single-kind query") + } + + out.KVValue = stmt.ColumnText(0) + return nil + } + + err := sqlitegen.ExecStmt(conn, query, before, onStep) + if err != nil { + err = fmt.Errorf("failed query: GetServedGroupID: %w", err) + } + + return out, err +} diff --git a/backend/cmd/mintter-site/sitesql/queries.gensum b/backend/cmd/mintter-site/sitesql/queries.gensum new file mode 100644 index 0000000000..97e71c6456 --- /dev/null +++ b/backend/cmd/mintter-site/sitesql/queries.gensum @@ -0,0 +1,2 @@ +srcs: aa43be8cc99dd78bbf728cee50b0b5eb +outs: 8758b18717cb7ac28ae1bb1759ead80e diff --git a/backend/cmd/mintter-site/sitesql/queries.go b/backend/cmd/mintter-site/sitesql/queries.go new file mode 100644 index 0000000000..c0b7392a9a --- /dev/null +++ b/backend/cmd/mintter-site/sitesql/queries.go @@ -0,0 +1,49 @@ +// Package sitesql implements all the database related functions. +package sitesql + +import ( + s "mintter/backend/daemon/storage" + "mintter/backend/pkg/sqlitegen" + "mintter/backend/pkg/sqlitegen/qb" + "os" +) + +var _ = generateQueries + +const ( + // SiteRegistrationLinkKey is the column name of the meta table where we store the registration link. + SiteRegistrationLinkKey = "site_registration_link" + // SiteGroupIDKey is the group ID this site is serving. This is populated once the site is remotely initialized with the secret link. + SiteGroupIDKey = "site_group_id" + // SiteGroupVersionKey is the specific versiont of the group this site is serving. This may change through the life of the site as editors update it. + SiteGroupVersionKey = "site_group_version" +) + +//go:generate gorun -tags codegen generateQueries +func generateQueries() error { + code, err := sqlitegen.CodegenQueries("sitesql", + qb.MakeQuery(s.Schema, "SetServedGroupID", sqlitegen.QueryKindExec, + "INSERT OR REPLACE INTO", s.KV, qb.ListColShort( + s.KVKey, + s.KVValue, + ), '\n', + "VALUES", qb.List( + "'"+SiteGroupIDKey+"'", + qb.Var("link", sqlitegen.TypeText), + ), + ), + + qb.MakeQuery(s.Schema, "GetServedGroupID", sqlitegen.QueryKindSingle, + "SELECT", qb.Results( + qb.ResultCol(s.KVValue), + ), + "FROM", s.KV, + "WHERE", s.KVKey, "='"+SiteGroupIDKey+"'", + ), + ) + if err != nil { + return err + } + + return os.WriteFile("queries.gen.go", code, 0600) +} diff --git a/backend/cmd/mintter-site/sitesql/sitesql.go b/backend/cmd/mintter-site/sitesql/sitesql.go new file mode 100644 index 0000000000..25e26f91f1 --- /dev/null +++ b/backend/cmd/mintter-site/sitesql/sitesql.go @@ -0,0 +1,29 @@ +package sitesql + +import ( + "mintter/backend/hyper/hypersql" + + "crawshaw.io/sqlite" +) + +func ensurePublicKey(conn *sqlite.Conn, key []byte) (int64, error) { + res, err := hypersql.PublicKeysLookupID(conn, key) + if err != nil { + return 0, err + } + + if res.PublicKeysID > 0 { + return res.PublicKeysID, nil + } + + ins, err := hypersql.PublicKeysInsert(conn, key) + if err != nil { + return 0, err + } + + if ins.PublicKeysID <= 0 { + panic("BUG: failed to insert key for some reason") + } + + return ins.PublicKeysID, nil +} diff --git a/backend/cmd/mintterd/main.go b/backend/cmd/mintterd/main.go index 5624361efc..2122779a18 100644 --- a/backend/cmd/mintterd/main.go +++ b/backend/cmd/mintterd/main.go @@ -31,7 +31,7 @@ func main() { fs := flag.NewFlagSet("mintterd", flag.ExitOnError) cfg := config.Default() - config.SetupFlags(fs, &cfg) + cfg.BindFlags(fs) // We parse flags twice here, once without the config file setting, and then with it. // This is because we want the config file to be in the repo path, which can be changed @@ -42,11 +42,11 @@ func main() { return err } - if err := cfg.ExpandRepoPath(); err != nil { + if err := cfg.Base.ExpandDataDir(); err != nil { return err } - cfgFile, err := config.EnsureConfigFile(cfg.RepoPath) + cfgFile, err := config.EnsureConfigFile(cfg.Base.DataDir) if err != nil { return err } @@ -67,7 +67,11 @@ func main() { defer sentry.Flush(2 * time.Second) } - app, err := daemon.Load(ctx, cfg, + dir, err := daemon.InitRepo(cfg.Base.DataDir, nil) + if err != nil { + return err + } + app, err := daemon.Load(ctx, cfg, dir, grpc.ChainUnaryInterceptor( otelgrpc.UnaryServerInterceptor(), daemon.GRPCDebugLoggingInterceptor(), diff --git a/backend/cmd/minttergw/main.go b/backend/cmd/minttergw/main.go deleted file mode 100644 index 959372e867..0000000000 --- a/backend/cmd/minttergw/main.go +++ /dev/null @@ -1,126 +0,0 @@ -// Package main implements main script to run mintter-gateway daemon. -package main - -import ( - "context" - "errors" - "flag" - "fmt" - "os" - "strings" - - "mintter/backend/config" - "mintter/backend/core" - "mintter/backend/daemon" - accounts "mintter/backend/genproto/accounts/v1alpha" - protodaemon "mintter/backend/genproto/daemon/v1alpha" - - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/burdiyan/go/mainutil" - "github.com/peterbourgon/ff/v3" -) - -func main() { - const envVarPrefix = "MINTTER" - - mainutil.Run(func() error { - ctx := mainutil.TrapSignals() - - fs := flag.NewFlagSet("gateway", flag.ExitOnError) - - cfg := config.Default() - cfg.P2P.NoListing = true - config.SetupFlags(fs, &cfg) - - // We parse flags twice here, once without the config file setting, and then with it. - // This is because we want the config file to be in the repo path, which can be changed - // with flags or env vars. We don't allow setting a config file explicitly, but the repo path - // can change. We need to know the requested repo path in the first place, and then figure out the config file. - - if err := ff.Parse(fs, os.Args[1:], ff.WithEnvVarPrefix(envVarPrefix)); err != nil { - return err - } - - if err := cfg.ExpandRepoPath(); err != nil { - return err - } - - cfgFile, err := config.EnsureConfigFile(cfg.RepoPath) - if err != nil { - return err - } - - if err := ff.Parse(fs, os.Args[1:], - ff.WithEnvVarPrefix(envVarPrefix), - ff.WithConfigFileParser(ff.PlainParser), - ff.WithConfigFile(cfgFile), - ff.WithAllowMissingConfigFile(false), - ); err != nil { - return err - } - - app, err := daemon.Load(ctx, cfg, daemon.WithMiddleware(gwEssentials)) - if err != nil { - return err - } - - const mnemonicWords = 12 - mnemonic, err := core.NewBIP39Mnemonic(mnemonicWords) - if err != nil { - return err - } - - _, err = app.RPC.Daemon.Register(ctx, &protodaemon.RegisterRequest{ - Mnemonic: mnemonic, - Passphrase: "", - }) - stat, ok := status.FromError(err) - if !ok && stat.Code() != codes.AlreadyExists { - return err - } - - _, err = app.Storage.Identity().Await(ctx) - if err != nil { - return err - } - const alias = "Web gateway" - const bio = "Find me at https://www.mintter.com" - acc, err := app.RPC.Accounts.UpdateProfile(ctx, &accounts.Profile{ - Alias: alias, - Bio: bio, - }) - if err != nil { - return err - } - if acc.Profile.Alias != alias || acc.Profile.Bio != bio { - return fmt.Errorf("unexpected alias/bio. %s", acc.Profile.Alias+". "+acc.Profile.Bio) - } - err = app.Wait() - if errors.Is(err, context.Canceled) { - return nil - } - - return err - }) -} - -// GwEssentials is a middleware to restrict incoming grpc calls to bare minimum for the gateway to work. -func gwEssentials(ctx context.Context, - req interface{}, - info *grpc.UnaryServerInfo, - handler grpc.UnaryHandler) (interface{}, error) { - methodSplitted := strings.Split(info.FullMethod, "/") - if len(methodSplitted) < 2 || (strings.ToLower(methodSplitted[len(methodSplitted)-1]) != "getpublication" && - strings.ToLower(methodSplitted[len(methodSplitted)-1]) != "listcitations" && - strings.ToLower(methodSplitted[len(methodSplitted)-1]) != "getaccount") { - return nil, fmt.Errorf("method: %s not allowed", info.FullMethod) - } - - // Calls the handler - h, err := handler(ctx, req) - - return h, err -} diff --git a/backend/cmd/mkdb/main.go b/backend/cmd/mkdb/main.go new file mode 100644 index 0000000000..ef52fca3e5 --- /dev/null +++ b/backend/cmd/mkdb/main.go @@ -0,0 +1,64 @@ +// Program mkdb is supposed to be executed with `go run` from the root of the repository. +// It will create a (more or less) deterministic snapshot of the current schema of our database, +// which we can then use in tests to verify our migration scripts actually end up where we want them to. +package main + +import ( + "context" + "errors" + "fmt" + "mintter/backend/config" + "mintter/backend/core/coretest" + "mintter/backend/daemon" + "os" + + "github.com/burdiyan/go/mainutil" +) + +func main() { + mainutil.Run(run) +} + +func run() error { + ctx, cancel := context.WithCancel(mainutil.TrapSignals()) + defer cancel() + + alice := coretest.NewTester("alice") + + cfg := config.Default() + cfg.P2P.NoRelay = true + cfg.P2P.BootstrapPeers = nil + cfg.Base.DataDir = "/tmp/mintter-test-db-snapshot" + + if err := os.RemoveAll(cfg.Base.DataDir); err != nil { + return err + } + + if err := os.MkdirAll(cfg.Base.DataDir, 0750); err != nil { + return err + } + + dir, err := daemon.InitRepo(cfg.Base.DataDir, alice.Device.Wrapped()) + if err != nil { + return err + } + + app, err := daemon.Load(ctx, cfg, dir) + if err != nil { + return err + } + + if err := app.RPC.Daemon.RegisterAccount(ctx, alice.Account); err != nil { + return err + } + + cancel() + + err = app.Wait() + fmt.Println("Database has been saved in:", cfg.Base.DataDir) + if errors.Is(err, context.Canceled) { + return nil + } + + return err +} diff --git a/backend/cmd/monitord/server/checks.go b/backend/cmd/monitord/server/checks.go index d6fe7e577d..96fce51c95 100644 --- a/backend/cmd/monitord/server/checks.go +++ b/backend/cmd/monitord/server/checks.go @@ -4,17 +4,13 @@ package server import ( "context" "fmt" - "io" - documents "mintter/backend/genproto/documents/v1alpha" - "net/http" + groups "mintter/backend/daemon/api/groups/v1alpha" "time" - "mintter/backend/mttnet" - peer "github.com/libp2p/go-libp2p/core/peer" peerstore "github.com/libp2p/go-libp2p/core/peerstore" ping "github.com/libp2p/go-libp2p/p2p/protocol/ping" - "google.golang.org/protobuf/encoding/protojson" + "github.com/multiformats/go-multiaddr" ) func (s *Srv) checkP2P(ctx context.Context, peer peer.AddrInfo, numPings int) (time.Duration, error) { @@ -60,44 +56,28 @@ func (s *Srv) checkP2P(ctx context.Context, peer peer.AddrInfo, numPings int) (t } func (s *Srv) checkMintterAddrs(ctx context.Context, hostname, mustInclude string) (info peer.AddrInfo, err error) { - resp, err := s.getSiteInfoHTTP(ctx, hostname) - if err != nil { - return - } - info, err = mttnet.AddrInfoFromStrings(resp.Addresses[0]) // only TCP which is the first one + resp, err := groups.GetSiteInfoHTTP(ctx, nil, hostname) if err != nil { - return + return info, err } - return -} - -func (s *Srv) getSiteInfoHTTP(ctx context.Context, SiteHostname string) (*documents.SiteDiscoveryConfig, error) { - requestURL := fmt.Sprintf("%s/%s", SiteHostname, mttnet.WellKnownPath) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil) - if err != nil { - return nil, fmt.Errorf("could not create request to well-known site: %w ", err) + if resp.PeerInfo == nil { + return info, fmt.Errorf("no peer info got from site") } - res, err := http.DefaultClient.Do(req) + pid, err := peer.Decode(resp.PeerInfo.PeerId) if err != nil { - return nil, fmt.Errorf("could not contact to provided site [%s]: %w ", requestURL, err) - } - defer res.Body.Close() - if res.StatusCode < 200 || res.StatusCode > 299 { - return nil, fmt.Errorf("site info url [%s] not working. Status code: %d", requestURL, res.StatusCode) + return info, fmt.Errorf("failed to decode peer ID %s: %w", resp.PeerInfo.PeerId, err) } - data, err := io.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("failed to read json body: %w", err) + info.ID = pid + info.Addrs = make([]multiaddr.Multiaddr, len(resp.PeerInfo.Addrs)) + for i, as := range resp.PeerInfo.Addrs { + info.Addrs[i], err = multiaddr.NewMultiaddr(as) + if err != nil { + return info, err + } } - var resp documents.SiteDiscoveryConfig - - if err := protojson.Unmarshal(data, &resp); err != nil { - return nil, fmt.Errorf("failed to unmarshal JSON body: %w", err) - } - return &resp, nil + return info, nil } diff --git a/backend/config/config.go b/backend/config/config.go index 5d66e6f953..01d484f9a0 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -4,7 +4,6 @@ package config import ( "flag" "fmt" - "io/ioutil" "mintter/backend/ipfs" "os" "path/filepath" @@ -14,50 +13,83 @@ import ( "github.com/multiformats/go-multiaddr" ) -// Config for Mintter daemon. When adding or removing fields, -// adjust the DefaultConfig() and SetupFlags() accordingly. -type Config struct { - HTTPPort int - GRPCPort int - RepoPath string +// Base configuration. +type Base struct { + DataDir string LogLevel string +} - Identity Identity - Lndhub Lndhub - P2P P2P - Site Site - Syncing Syncing +// BindFlags binds the flags to the given FlagSet. +func (c *Base) BindFlags(fs *flag.FlagSet) { + fs.StringVar(&c.DataDir, "data-dir", c.DataDir, "Path to a directory where to store node data") + fs.StringVar(&c.LogLevel, "log-level", c.LogLevel, "Log verbosity debug | info | warning | error") +} + +// ExpandDataDir is used to expand the home directory in the data directory path. +func (c *Base) ExpandDataDir() error { + // We allow homedir expansion in the repo path. + if strings.HasPrefix(c.DataDir, "~") { + homedir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("failed to detect home directory: %w", err) + } + c.DataDir = strings.Replace(c.DataDir, "~", homedir, 1) + } + return nil +} + +// Config for our daemon. When adding or removing fields, +// adjust the Default() and BindFlags() accordingly. +type Config struct { + Base + + HTTP HTTP + GRPC GRPC + Lndhub Lndhub + P2P P2P + Syncing Syncing +} + +// BindFlags configures the given FlagSet with the existing values from the given Config +// and prepares the FlagSet to parse the flags into the Config. +// +// This function is assumed to be called after some default values were set on the given config. +// These values will be used as default values in flags. +// See Default() for the default config values. +func (c *Config) BindFlags(fs *flag.FlagSet) { + c.Base.BindFlags(fs) + c.HTTP.BindFlags(fs) + c.GRPC.BindFlags(fs) + c.Lndhub.BindFlags(fs) + c.P2P.BindFlags(fs) + c.Syncing.BindFlags(fs) } // Default creates a new default config. func Default() Config { return Config{ - HTTPPort: 55001, - GRPCPort: 55002, - RepoPath: "~/.mtt", - LogLevel: "debug", + HTTP: HTTP{ + Port: 55001, + }, + GRPC: GRPC{ + Port: 55002, + }, + Base: Base{ + DataDir: "~/.mtt", + LogLevel: "debug", + }, Lndhub: Lndhub{ Mainnet: false, }, - - Identity: Identity{ - DeviceKeyPath: "", - NoAccountWait: false, - }, - P2P: P2P{ BootstrapPeers: ipfs.DefaultBootstrapPeers(), Port: 55000, RelayBackoff: time.Minute * 3, }, - Site: Site{ - InviteTokenExpirationDelay: time.Hour * 24 * 7, - }, Syncing: Syncing{ WarmupDuration: time.Second * 20, Interval: time.Minute, TimeoutPerPeer: time.Minute * 2, - NoInbound: false, }, } } @@ -105,63 +137,23 @@ func newAddrsFlag(val []multiaddr.Multiaddr, p *[]multiaddr.Multiaddr) flag.Valu return (*addrsFlag)(p) } -// SetupFlags configures the given FlagSet with the existing values from the given Config -// and prepares the FlagSet to parse the flags into the Config. -// -// This function is assumed to be called after some default values were set on the given config. -// These values will be used as default values in flags. -// See Default() for the default config values. -func SetupFlags(fs *flag.FlagSet, cfg *Config) { - fs.IntVar(&cfg.HTTPPort, "http-port", cfg.HTTPPort, "Port to expose HTTP Server (including grpc-web)") - fs.IntVar(&cfg.GRPCPort, "grpc-port", cfg.GRPCPort, "Port to expose gRPC server") - fs.StringVar(&cfg.RepoPath, "repo-path", cfg.RepoPath, "Path to where to store node data") - fs.StringVar(&cfg.LogLevel, "log-level", cfg.LogLevel, "Log verbosity debug | info | warning | error") - - fs.StringVar(&cfg.Identity.DeviceKeyPath, "identity.devicekey-path", cfg.Identity.DeviceKeyPath, "Path to to read fixed device private key from") - fs.BoolVar(&cfg.Identity.NoAccountWait, "identity.no-account-wait", cfg.Identity.NoAccountWait, "If set, the daemon auto generates a random Account ID (if not found any in the database) and starts right away") - - fs.BoolVar(&cfg.Lndhub.Mainnet, "lndhub.mainnet", cfg.Lndhub.Mainnet, "Connect to the mainnet lndhub.go server") - - fs.IntVar(&cfg.P2P.Port, "p2p.port", cfg.P2P.Port, "Port to listen for incoming P2P connections") - fs.BoolVar(&cfg.P2P.NoRelay, "p2p.no-relay", cfg.P2P.NoRelay, "Disable libp2p circuit relay") - fs.Var(newAddrsFlag(cfg.P2P.BootstrapPeers, &cfg.P2P.BootstrapPeers), "p2p.bootstrap-peers", "Addresses for bootstrap nodes (comma separated)") - fs.Var(newAddrsFlag(cfg.P2P.AnnounceAddrs, &cfg.P2P.AnnounceAddrs), "p2p.announce-addrs", "Addresses will be announced for this node to be reachable at (comma separated multiaddresses format). overrides no-private-ips") - fs.BoolVar(&cfg.P2P.PublicReachability, "p2p.public-reachability", cfg.P2P.PublicReachability, "Force Reachability to public.") - fs.Var(newAddrsFlag(cfg.P2P.ListenAddrs, &cfg.P2P.ListenAddrs), "p2p.listen-addrs", "Addresses to be listen at (comma separated multiaddresses format)") - fs.BoolVar(&cfg.P2P.NoPrivateIps, "p2p.no-private-ips", cfg.P2P.NoPrivateIps, "Not announce local IPs.") - fs.BoolVar(&cfg.P2P.NoListing, "p2p.disable-listing", cfg.P2P.NoListing, "Disable listing documents when requested (stealth mode)") - fs.BoolVar(&cfg.P2P.NoMetrics, "p2p.no-metrics", cfg.P2P.NoMetrics, "Disable Prometheus metrics collection") - fs.DurationVar(&cfg.P2P.RelayBackoff, "p2p.relay-backoff", cfg.P2P.RelayBackoff, "The time the autorelay waits to reconnect after failing to obtain a reservation with a candidate") - - fs.BoolVar(&cfg.Site.NoAuth, "site.no-auth", cfg.Site.NoAuth, "Disable site authentication") - fs.StringVar(&cfg.Site.Hostname, "site.hostname", cfg.Site.Hostname, "Hostname of the site. If not provided then the daemon does not work as a site") - fs.StringVar(&cfg.Site.Title, "site.title", cfg.Site.Title, "Title of the site. Something brief and human readable to help understand the site") - fs.StringVar(&cfg.Site.OwnerID, "site.owner-id", cfg.Site.OwnerID, "Account ID of the owner of this site. If not provided, the owner ID will be this node's account ID") - fs.DurationVar(&cfg.Site.InviteTokenExpirationDelay, "site.token-expiration-delay", cfg.Site.InviteTokenExpirationDelay, "The expiration time delay when creating a new invite token") - - fs.DurationVar(&cfg.Syncing.WarmupDuration, "syncing.warmup-duration", cfg.Syncing.WarmupDuration, "Time to wait before the first sync loop iteration") - fs.DurationVar(&cfg.Syncing.Interval, "syncing.interval", cfg.Syncing.Interval, "Periodic interval at which sync loop is triggered") - fs.DurationVar(&cfg.Syncing.TimeoutPerPeer, "syncing.timeout-per-peer", cfg.Syncing.TimeoutPerPeer, "Maximum duration for syncing with a single peer") - fs.BoolVar(&cfg.Syncing.NoInbound, "syncing.disable-inbound", cfg.Syncing.NoInbound, "Not syncing inbound content via P2P, only syncs to remote peers. IF this is a site, however still admits content when published") -} - -// ExpandRepoPath is used to expand the home directory in the repo path. -func (c *Config) ExpandRepoPath() error { - // We allow homedir expansion in the repo path. - if strings.HasPrefix(c.RepoPath, "~") { - homedir, err := os.UserHomeDir() - if err != nil { - return fmt.Errorf("failed to detect home directory: %w", err) - } - c.RepoPath = strings.Replace(c.RepoPath, "~", homedir, 1) - } - return nil +// HTTP configuration. +type HTTP struct { + Port int +} + +// BindFlags binds the flags to the given FlagSet. +func (c *HTTP) BindFlags(fs *flag.FlagSet) { + fs.IntVar(&c.Port, "http.port", c.Port, "Port for the HTTP server (including grpc-web)") } -// Identity related config. For field descriptions see SetupFlags(). -type Identity struct { - DeviceKeyPath string - NoAccountWait bool +// GRPC configuration. +type GRPC struct { + Port int +} + +func (c *GRPC) BindFlags(fs *flag.FlagSet) { + fs.IntVar(&c.Port, "grpc.port", c.Port, "Port for the gRPC server") } // Lndhub related config. For field descriptions see SetupFlags(). @@ -169,38 +161,51 @@ type Lndhub struct { Mainnet bool } -// Syncing related config. For field descriptions see SetupFlags(). +// BindFlags binds the flags to the given FlagSet. +func (c *Lndhub) BindFlags(fs *flag.FlagSet) { + fs.BoolVar(&c.Mainnet, "lndhub.mainnet", c.Mainnet, "Connect to the mainnet lndhub.go server") +} + +// Syncing configuration. For field descriptions see SetupFlags(). type Syncing struct { WarmupDuration time.Duration Interval time.Duration TimeoutPerPeer time.Duration - // NoInbound disables syncing content to the remote peer from our peer. - // If false, then documents get synced in both directions. - NoInbound bool + Disabled bool } -// Site configuration. In case the daemon is deployed in a site. -// For field descriptions see SetupFlags(). -type Site struct { - Hostname string - InviteTokenExpirationDelay time.Duration - OwnerID string - Title string - NoAuth bool +// BindFlags binds the flags to the given FlagSet. +func (c *Syncing) BindFlags(fs *flag.FlagSet) { + fs.DurationVar(&c.WarmupDuration, "syncing.warmup-duration", c.WarmupDuration, "Time to wait before the first sync loop iteration") + fs.DurationVar(&c.Interval, "syncing.interval", c.Interval, "Periodic interval at which sync loop is triggered") + fs.DurationVar(&c.TimeoutPerPeer, "syncing.timeout-per-peer", c.TimeoutPerPeer, "Maximum duration for syncing with a single peer") + fs.BoolVar(&c.Disabled, "syncing.disabled", c.Disabled, "Disables periodic syncing") } // P2P configuration. For field descriptions see SetupFlags(). type P2P struct { - Port int - NoRelay bool - BootstrapPeers []multiaddr.Multiaddr - PublicReachability bool - NoPrivateIps bool - NoListing bool - NoMetrics bool - RelayBackoff time.Duration - AnnounceAddrs []multiaddr.Multiaddr - ListenAddrs []multiaddr.Multiaddr + Port int + NoRelay bool + BootstrapPeers []multiaddr.Multiaddr + ForceReachabilityPublic bool + NoPrivateIps bool + NoMetrics bool + RelayBackoff time.Duration + AnnounceAddrs []multiaddr.Multiaddr + ListenAddrs []multiaddr.Multiaddr +} + +// BindFlags binds the flags to the given FlagSet. +func (p2p *P2P) BindFlags(fs *flag.FlagSet) { + fs.IntVar(&p2p.Port, "p2p.port", p2p.Port, "Port to listen for incoming P2P connections") + fs.BoolVar(&p2p.NoRelay, "p2p.no-relay", p2p.NoRelay, "Disable libp2p circuit relay") + fs.Var(newAddrsFlag(p2p.BootstrapPeers, &p2p.BootstrapPeers), "p2p.bootstrap-peers", "Multiaddrs for bootstrap nodes (comma separated)") + fs.Var(newAddrsFlag(p2p.AnnounceAddrs, &p2p.AnnounceAddrs), "p2p.announce-addrs", "Multiaddrs this node will announce as being reachable at (comma separated)") + fs.BoolVar(&p2p.ForceReachabilityPublic, "p2p.force-reachability-public", p2p.ForceReachabilityPublic, "Force the node into thinking it's publicly reachable") + fs.Var(newAddrsFlag(p2p.ListenAddrs, &p2p.ListenAddrs), "p2p.listen-addrs", "Addresses to be listen at (comma separated multiaddresses format)") + fs.BoolVar(&p2p.NoPrivateIps, "p2p.no-private-ips", p2p.NoPrivateIps, "Avoid announcing private IP addresses (ignored when using -p2p.announce-addrs)") + fs.BoolVar(&p2p.NoMetrics, "p2p.no-metrics", p2p.NoMetrics, "Disable Prometheus metrics collection") + fs.DurationVar(&p2p.RelayBackoff, "p2p.relay-backoff", p2p.RelayBackoff, "The time the autorelay waits to reconnect after failing to obtain a reservation with a candidate") } // NoBootstrap indicates whether bootstrap nodes are configured. @@ -226,7 +231,7 @@ func EnsureConfigFile(repoPath string) (filename string, err error) { } if os.IsNotExist(err) { - if err := ioutil.WriteFile(filename, []byte(`# Config file for the mintterd program. + if err := os.WriteFile(filename, []byte(`# Config file for the mintterd program. # You can set any CLI flags here, one per line with a space between key and value. `), 0600); err != nil { return "", err diff --git a/backend/core/coretest/coretest.go b/backend/core/coretest/coretest.go index f28e162d81..66204eb2cc 100644 --- a/backend/core/coretest/coretest.go +++ b/backend/core/coretest/coretest.go @@ -53,7 +53,7 @@ func NewTester(name string) Tester { panic(err) } - dev, err := core.NewKeyPair(core.CodecDeviceKey, dpriv.(*crypto.Ed25519PrivateKey)) + dev, err := core.NewKeyPair(dpriv.(*crypto.Ed25519PrivateKey)) if err != nil { panic(err) } @@ -63,7 +63,7 @@ func NewTester(name string) Tester { panic(err) } - acc, err := core.NewKeyPair(core.CodecAccountKey, apriv.(*crypto.Ed25519PrivateKey)) + acc, err := core.NewKeyPair(apriv.(*crypto.Ed25519PrivateKey)) if err != nil { panic(err) } diff --git a/backend/core/coretest/coretest_test.go b/backend/core/coretest/coretest_test.go index 9974d22668..f025bfaa1e 100644 --- a/backend/core/coretest/coretest_test.go +++ b/backend/core/coretest/coretest_test.go @@ -15,7 +15,6 @@ func TestKeys(t *testing.T) { require.NoError(t, err) require.True(t, alice.Device.ID() == pid) - require.True(t, alice.Device.CID().Equals(peer.ToCid(pid))) } func TestEncoding(t *testing.T) { @@ -24,7 +23,7 @@ func TestEncoding(t *testing.T) { data, err := alice.Account.MarshalBinary() require.NoError(t, err) - pk, err := core.ParsePublicKey(core.CodecAccountKey, data) + pk, err := core.ParsePublicKey(data) require.NoError(t, err) require.Equal(t, alice.Account.String(), pk.String()) } diff --git a/backend/core/crypto.go b/backend/core/crypto.go index 6f1323de79..31e847f4e9 100644 --- a/backend/core/crypto.go +++ b/backend/core/crypto.go @@ -11,14 +11,6 @@ import ( "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/peer" - "github.com/multiformats/go-multihash" -) - -// Multicodecs. -const ( - CodecDeviceKey = cid.Libp2pKey - // TODO: need to register this codec withing the multicodecs repo table. - CodecAccountKey = 1091161161 ) // Ensure interface implementations. @@ -72,15 +64,14 @@ type KeyID = peer.ID // PublicKey is the public part of a KeyPair. type PublicKey struct { - k crypto.PubKey - id KeyID - codec uint64 + k crypto.PubKey + id KeyID abbrev uint64 } // NewPublicKey creates a new public key from an existing Ed25519 public key. -func NewPublicKey(codec uint64, pub crypto.PubKey) (pk PublicKey, err error) { +func NewPublicKey(pub crypto.PubKey) (pk PublicKey, err error) { _, ok := pub.(*crypto.Ed25519PublicKey) if !ok { return pk, fmt.Errorf("only Ed25519 public keys are supported, but got %T", pub) @@ -104,7 +95,6 @@ func NewPublicKey(codec uint64, pub crypto.PubKey) (pk PublicKey, err error) { return PublicKey{ k: pub, id: pid, - codec: codec, abbrev: *(*uint64)(unsafe.Pointer(&b)), }, nil } @@ -122,11 +112,11 @@ func PublicKeyFromCID(c cid.Cid) (pk PublicKey, err error) { return pk, err } - return NewPublicKey(c.Prefix().Codec, pub.(*crypto.Ed25519PublicKey)) + return NewPublicKey(pub.(*crypto.Ed25519PublicKey)) } // ParsePublicKey parses existing libp2p-encoded key material. -func ParsePublicKey(codec uint64, data []byte) (pk PublicKey, err error) { +func ParsePublicKey(data []byte) (pk PublicKey, err error) { pub, err := crypto.UnmarshalPublicKey(data) if err != nil { return pk, err @@ -136,7 +126,7 @@ func ParsePublicKey(codec uint64, data []byte) (pk PublicKey, err error) { return pk, fmt.Errorf("only ed25519 keys are supported") } - return NewPublicKey(codec, pub.(*crypto.Ed25519PublicKey)) + return NewPublicKey(pub.(*crypto.Ed25519PublicKey)) } // Abbrev returns the abbreviated form of the public key, @@ -152,16 +142,6 @@ func (pk PublicKey) PeerID() peer.ID { return pk.id } -// CID returns CID representation of the public key. -func (pk PublicKey) CID() cid.Cid { - mh, err := multihash.Cast([]byte(pk.id)) - if err != nil { - panic(err) - } - - return cid.NewCidV1(pk.codec, mh) -} - // String creates string representation of the public key. func (pk PublicKey) String() string { return pk.Principal().String() @@ -172,11 +152,6 @@ func (pk PublicKey) Principal() Principal { return PrincipalFromPubKey(pk.k) } -// Codec returns multicodec of the public key. -func (pk PublicKey) Codec() uint64 { - return pk.codec -} - // Verify implements Verifier. func (pk PublicKey) Verify(data []byte, s Signature) error { return s.verify(pk.k, data) @@ -205,24 +180,24 @@ type KeyPair struct { } // NewKeyPairRandom creates a new random KeyPair with a given multicodec prefix. -func NewKeyPairRandom(codec uint64) (kp KeyPair, err error) { +func NewKeyPairRandom() (kp KeyPair, err error) { priv, _, err := crypto.GenerateEd25519Key(rand.Reader) if err != nil { return kp, fmt.Errorf("failed to generate device private key: %w", err) } - return NewKeyPair(codec, priv.(*crypto.Ed25519PrivateKey)) + return NewKeyPair(priv.(*crypto.Ed25519PrivateKey)) } // NewKeyPair creates a new KeyPair with a given multicodec prefix from an existing instance // of the private key. At the moment only Ed25519 keys are supported. -func NewKeyPair(codec uint64, priv crypto.PrivKey) (kp KeyPair, err error) { +func NewKeyPair(priv crypto.PrivKey) (kp KeyPair, err error) { _, ok := priv.(*crypto.Ed25519PrivateKey) if !ok { return kp, fmt.Errorf("only ed25519 keys are supported") } - pub, err := NewPublicKey(codec, priv.GetPublic().(*crypto.Ed25519PublicKey)) + pub, err := NewPublicKey(priv.GetPublic().(*crypto.Ed25519PublicKey)) if err != nil { return kp, err } diff --git a/backend/core/identity.go b/backend/core/identity.go index 16bfaaa6ca..297807e26b 100644 --- a/backend/core/identity.go +++ b/backend/core/identity.go @@ -16,14 +16,6 @@ type Identity struct { } func NewIdentity(account PublicKey, device KeyPair) Identity { - if account.Codec() != CodecAccountKey { - panic("not account key") - } - - if device.Codec() != CodecDeviceKey { - panic("not device key") - } - return Identity{ account: account, device: device, @@ -69,7 +61,7 @@ func AccountFromSeed(rand []byte) (KeyPair, error) { return KeyPair{}, err } - return NewKeyPair(CodecAccountKey, priv.(*crypto.Ed25519PrivateKey)) + return NewKeyPair(priv.(*crypto.Ed25519PrivateKey)) } // NewBIP39Mnemonic creates a new random BIP-39 compatible mnemonic words. diff --git a/backend/daemon/api/accounts/v1alpha/accounts.go b/backend/daemon/api/accounts/v1alpha/accounts.go index 5df2344053..e2e8466f30 100644 --- a/backend/daemon/api/accounts/v1alpha/accounts.go +++ b/backend/daemon/api/accounts/v1alpha/accounts.go @@ -10,6 +10,7 @@ import ( "mintter/backend/hyper" "mintter/backend/hyper/hypersql" "mintter/backend/pkg/future" + "strings" "crawshaw.io/sqlite" "github.com/ipfs/go-cid" @@ -193,6 +194,14 @@ func UpdateProfile(ctx context.Context, me core.Identity, blobs *hyper.Storage, patch := map[string]any{} + if in.Alias != "" { + parts := strings.Fields(in.Alias) + if len(parts) != 1 { + return fmt.Errorf("alias must be a single word: got = %q", in.Alias) + } + in.Alias = parts[0] + } + v, ok := e.Get("alias") if (ok && v.(string) != in.Alias) || (!ok && in.Alias != "") { patch["alias"] = in.Alias diff --git a/backend/daemon/api/apis.go b/backend/daemon/api/apis.go index f87348b8cc..f029816098 100644 --- a/backend/daemon/api/apis.go +++ b/backend/daemon/api/apis.go @@ -3,7 +3,6 @@ package api import ( "context" "fmt" - "mintter/backend/config" accounts "mintter/backend/daemon/api/accounts/v1alpha" daemon "mintter/backend/daemon/api/daemon/v1alpha" documents "mintter/backend/daemon/api/documents/v1alpha" @@ -28,7 +27,6 @@ type Server struct { Daemon *daemon.Server Documents *documents.Server Networking *networking.Server - Site *mttnet.Server Entities *entities.Server Groups *groups.Server } @@ -42,7 +40,6 @@ func New( node *future.ReadOnly[*mttnet.Node], sync *future.ReadOnly[*syncing.Service], wallet *wallet.Service, - cfg config.Site, ) Server { doSync := func() error { s, ok := sync.Get() @@ -59,17 +56,14 @@ func New( return nil } - documentsSrv := documents.NewServer(repo.Identity(), db, &lazyDiscoverer{sync: sync, net: node}, nil) - siteSrv := mttnet.NewServer(ctx, cfg, node, documentsSrv, &lazyDiscoverer{sync: sync}) - documentsSrv.RemoteCaller = siteSrv + documentsSrv := documents.NewServer(repo.Identity(), db, &lazyDiscoverer{sync: sync, net: node}) return Server{ Accounts: accounts.NewServer(repo.Identity(), blobs), Daemon: daemon.NewServer(repo, blobs, wallet, doSync), Documents: documentsSrv, Networking: networking.NewServer(node), - Site: siteSrv, Entities: entities.NewServer(blobs, &lazyDiscoverer{sync: sync}), - Groups: groups.NewServer(repo.Identity(), blobs, node), + Groups: groups.NewServer(repo.Identity(), groups.NewSQLiteDB(db), blobs, node), } } diff --git a/backend/daemon/api/daemon/v1alpha/daemon.go b/backend/daemon/api/daemon/v1alpha/daemon.go index fdde4e210b..7d16cbcda1 100644 --- a/backend/daemon/api/daemon/v1alpha/daemon.go +++ b/backend/daemon/api/daemon/v1alpha/daemon.go @@ -92,11 +92,7 @@ func (srv *Server) Register(ctx context.Context, req *daemon.RegisterRequest) (* // RegisterAccount performs registration given an existing account key pair. func (srv *Server) RegisterAccount(ctx context.Context, acc core.KeyPair) error { - if err := srv.repo.CommitAccount(acc.PublicKey); err != nil { - return err - } - - _, err := Register(ctx, srv.blobs, acc, srv.repo.Device().PublicKey, time.Now().UTC()) + _, err := RegisterWithRepo(ctx, srv.repo, srv.blobs, acc, srv.repo.Device().PublicKey, time.Now().UTC()) if err != nil { return err } @@ -108,6 +104,15 @@ func (srv *Server) RegisterAccount(ctx context.Context, acc core.KeyPair) error return nil } +// RegisterWithRepo creates key delegation from account to device and stores the keys in files. +func RegisterWithRepo(ctx context.Context, repo Repo, bs *hyper.Storage, acc core.KeyPair, device core.PublicKey, at time.Time) (cid.Cid, error) { + if err := repo.CommitAccount(acc.PublicKey); err != nil { + return cid.Undef, err + } + + return Register(ctx, bs, acc, device, at) +} + // Register creates key delegation from account to device. func Register(ctx context.Context, bs *hyper.Storage, account core.KeyPair, device core.PublicKey, at time.Time) (cid.Cid, error) { kd, err := hyper.NewKeyDelegation(account, device, time.Now().UTC()) diff --git a/backend/daemon/api/documents/v1alpha/document_model.go b/backend/daemon/api/documents/v1alpha/document_model.go index 1aeddf6131..7dad4f1a4c 100644 --- a/backend/daemon/api/documents/v1alpha/document_model.go +++ b/backend/daemon/api/documents/v1alpha/document_model.go @@ -167,16 +167,6 @@ func (dm *docModel) SetTitle(title string) error { return nil } -func (dm *docModel) SetWebURL(url string) error { - v, ok := dm.e.Get("webURL") - if ok && v.(string) == url { - return nil - } - - dm.patch["webURL"] = url - return nil -} - func (dm *docModel) DeleteBlock(block string) error { _, err := dm.tree.MoveLocal(dm.nextHLC.Pack(), len(dm.tree.localMoves), block, TrashNodeID, "") return err @@ -341,13 +331,6 @@ func (dm *docModel) hydrate(ctx context.Context, blobs *hyper.Storage) (*documen } } - { - v, ok := e.Get("webURL") - if ok { - docpb.WebUrl = v.(string) - } - } - // Loading editors is a bit cumbersome because we need to go over key delegations. { seenAccounts := map[string]struct{}{} diff --git a/backend/daemon/api/documents/v1alpha/documents.go b/backend/daemon/api/documents/v1alpha/documents.go index d031940437..a57d720323 100644 --- a/backend/daemon/api/documents/v1alpha/documents.go +++ b/backend/daemon/api/documents/v1alpha/documents.go @@ -32,30 +32,21 @@ type Discoverer interface { Connect(context.Context, peer.AddrInfo) error } -// RemoteCaller is an interface for not having to pass a full-fledged sites service, -// just the remote functions that need to be called from the local server. -type RemoteCaller interface { - RedeemInviteToken(context.Context, *documents.RedeemInviteTokenRequest) (*documents.RedeemInviteTokenResponse, error) - ListWebPublications(ctx context.Context, in *documents.ListWebPublicationsRequest) (*documents.ListWebPublicationsResponse, error) -} - // Server implements DocumentsServer gRPC API. type Server struct { - db *sqlitex.Pool - me *future.ReadOnly[core.Identity] - disc Discoverer - RemoteCaller RemoteCaller - blobs *hyper.Storage + db *sqlitex.Pool + me *future.ReadOnly[core.Identity] + disc Discoverer + blobs *hyper.Storage } // NewServer creates a new RPC handler. -func NewServer(me *future.ReadOnly[core.Identity], db *sqlitex.Pool, disc Discoverer, remoteCaller RemoteCaller) *Server { +func NewServer(me *future.ReadOnly[core.Identity], db *sqlitex.Pool, disc Discoverer) *Server { srv := &Server{ - db: db, - me: me, - disc: disc, - RemoteCaller: remoteCaller, - blobs: hyper.NewStorage(db, logging.New("mintter/hyper", "debug")), + db: db, + me: me, + disc: disc, + blobs: hyper.NewStorage(db, logging.New("mintter/hyper", "debug")), } return srv @@ -202,10 +193,6 @@ func (api *Server) UpdateDraft(ctx context.Context, in *documents.UpdateDraftReq if err := mut.ReplaceBlock(o.ReplaceBlock); err != nil { return nil, err } - case *documents.DocumentChange_SetWebUrl: - if err := mut.SetWebURL(o.SetWebUrl); err != nil { - return nil, err - } default: panic("BUG: unhandled document change") } @@ -355,6 +342,14 @@ func (api *Server) GetPublication(ctx context.Context, in *documents.GetPublicat return nil, err } + // TODO(burdiyan): if we are doing the discovery without a version, + // we'll wait until timeout because we don't know when to stop looking. + // Ideally we should only be discovering docs with specific version, + // but sometimes we don't want the latest version we can possibly find. + // In those cases, we could at least optimize the UI, and maybe display + // the document dynamically as we're finding it. Although that would require + // a lot of trickery between frontend and backend, it would optimize + // time to the first (more or less) meaningful result. if err := api.disc.DiscoverObject(ctx, eid, version); err != nil { return nil, status.Errorf(codes.NotFound, "failed to discover object %q at version %q", eid, version) } diff --git a/backend/daemon/api/documents/v1alpha/documents_test.go b/backend/daemon/api/documents/v1alpha/documents_test.go index 1e430abb5f..09e55576a7 100644 --- a/backend/daemon/api/documents/v1alpha/documents_test.go +++ b/backend/daemon/api/documents/v1alpha/documents_test.go @@ -89,7 +89,6 @@ func TestUpdateDraft_SimpleAttributes(t *testing.T) { require.Greater(t, draft.UpdateTime.AsTime().UnixMicro(), draft.CreateTime.AsTime().UnixMicro()) updated := updateDraft(ctx, t, api, draft.Id, []*documents.DocumentChange{ {Op: &documents.DocumentChange_SetTitle{SetTitle: "My new document title"}}, - {Op: &documents.DocumentChange_SetWebUrl{SetWebUrl: "https://example.com"}}, }) require.Equal(t, draft.CreateTime, updated.CreateTime) @@ -100,7 +99,6 @@ func TestUpdateDraft_SimpleAttributes(t *testing.T) { testutil.ProtoEqual(t, updated, got, "must get draft that was updated") require.Equal(t, "My new document title", got.Title) - require.Equal(t, "https://example.com", got.WebUrl) // Update again. updated = updateDraft(ctx, t, api, draft.Id, []*documents.DocumentChange{ @@ -111,7 +109,6 @@ func TestUpdateDraft_SimpleAttributes(t *testing.T) { testutil.ProtoEqual(t, updated, got, "must get draft that was updated") require.Equal(t, "My changed title", got.Title) - require.Equal(t, "https://example.com", got.WebUrl) } func TestUpdateDraft_WithBlocks(t *testing.T) { @@ -131,7 +128,6 @@ func TestUpdateDraft_WithBlocks(t *testing.T) { updated := updateDraft(ctx, t, api, draft.Id, []*documents.DocumentChange{ {Op: &documents.DocumentChange_SetTitle{SetTitle: "My new document title"}}, - {Op: &documents.DocumentChange_SetWebUrl{SetWebUrl: "https://example.com"}}, {Op: &documents.DocumentChange_MoveBlock_{MoveBlock: &documents.DocumentChange_MoveBlock{BlockId: "b1"}}}, {Op: &documents.DocumentChange_ReplaceBlock{ReplaceBlock: &documents.Block{ Id: "b1", @@ -147,7 +143,6 @@ func TestUpdateDraft_WithBlocks(t *testing.T) { testutil.ProtoEqual(t, updated, got, "must get draft that was updated") require.Equal(t, "My new document title", got.Title) - require.Equal(t, "https://example.com", got.WebUrl) require.Equal(t, "b1", got.Children[0].Block.Id, "block id must match") require.Nil(t, got.Children[0].Children, "block must not have children if not needed") require.Equal(t, "statement", got.Children[0].Block.Type, "block type must match") @@ -794,14 +789,12 @@ func TestPublisherAndEditors(t *testing.T) { DocumentId: draft.Id, Changes: []*documents.DocumentChange{ {Op: &documents.DocumentChange_SetTitle{SetTitle: "Document title"}}, - {Op: &documents.DocumentChange_SetWebUrl{SetWebUrl: "http://example.com"}}, }, }) require.NoError(t, err) draft, err = api.GetDraft(ctx, &documents.GetDraftRequest{DocumentId: draft.Id}) require.NoError(t, err) - require.Equal(t, "http://example.com", draft.WebUrl) require.Equal(t, "Document title", draft.Title) wantEditors := []string{api.me.MustGet().Account().Principal().String()} require.Equal(t, wantEditors, draft.Editors) @@ -868,7 +861,7 @@ func newTestDocsAPI(t *testing.T, name string) *Server { fut := future.New[core.Identity]() require.NoError(t, fut.Resolve(u.Identity)) - srv := NewServer(fut.ReadOnly, db, nil, nil) + srv := NewServer(fut.ReadOnly, db, nil) bs := hyper.NewStorage(db, logging.New("mintter/hyper", "debug")) _, err := daemon.Register(context.Background(), bs, u.Account, u.Device.PublicKey, time.Now()) require.NoError(t, err) diff --git a/backend/daemon/api/documents/v1alpha/web_publishing.go b/backend/daemon/api/documents/v1alpha/web_publishing.go deleted file mode 100644 index f4552022a8..0000000000 --- a/backend/daemon/api/documents/v1alpha/web_publishing.go +++ /dev/null @@ -1,169 +0,0 @@ -// Package documents provides the implementation of the Documents gRPC API. -package documents - -import ( - context "context" - "fmt" - "mintter/backend/core" - documents "mintter/backend/genproto/documents/v1alpha" - "mintter/backend/mttnet" - "mintter/backend/mttnet/sitesql" - "strings" - - "google.golang.org/grpc/metadata" - "google.golang.org/protobuf/types/known/emptypb" -) - -// AddSite checks if the provided site hostname is a valid Mintter site and if so, add it to the database. -func (api *Server) AddSite(ctx context.Context, in *documents.AddSiteRequest) (*documents.SiteConfig, error) { - if in.Hostname == "" { - return nil, fmt.Errorf("add site: empty hostname provided") - } - if strings.Contains(strings.ToLower(in.Hostname), "notallow") { - return nil, fmt.Errorf("add site: site " + in.Hostname + " is not a valid site") - } - if strings.Contains(strings.ToLower(in.Hostname), "taken") { - return nil, fmt.Errorf("add site: site " + in.Hostname + " already taken") - } - - resp, err := mttnet.GetSiteInfoHttp(in.Hostname) - if err != nil { - return nil, fmt.Errorf("add site: Could not get site [%s] info via http: %w", in.Hostname, err) - } - account, err := core.DecodePrincipal(resp.AccountId) - if err != nil { - return nil, fmt.Errorf("add site: got an invalid accountID [%s]: %w", resp.AccountId, err) - } - - info, err := mttnet.AddrInfoFromStrings(resp.Addresses...) - if err != nil { - return nil, fmt.Errorf("add site: couldn't parse multiaddress: %w", err) - } - - if err = api.disc.Connect(ctx, info); err != nil { - return nil, fmt.Errorf("add site: couldn't connect to the remote site via p2p: %w", err) - } - - conn, cancel, err := api.db.Conn(ctx) - if err != nil { - return nil, fmt.Errorf("add site: Cannot connect to internal db") - } - defer cancel() - - site, err := sitesql.GetSite(conn, in.Hostname) - if err != nil { - return nil, err - } - if site.SitesHostname == in.Hostname { - return nil, fmt.Errorf("add site: site %s was already added", in.Hostname) - } - - // make it a proxy call since we want to talk with the site by attaching headers - header := metadata.New(map[string]string{mttnet.TargetSiteHostnameHeader: in.Hostname}) - ctx = metadata.NewIncomingContext(ctx, header) // Usually, the headers are written by the client in the outgoing context and server receives them in the incoming. But here we are writing the server directly - ctx = context.WithValue(ctx, mttnet.GRPCOriginAcc, resp.AccountId) - var role documents.Member_Role - if in.InviteToken != "" { - res, err := api.RemoteCaller.RedeemInviteToken(ctx, &documents.RedeemInviteTokenRequest{ - Token: in.InviteToken, - }) - if err != nil { - return nil, fmt.Errorf("add site: couldn't redeem the attached token: %w", err) - } - - role = res.Role - } else { - res, err := api.RemoteCaller.RedeemInviteToken(ctx, &documents.RedeemInviteTokenRequest{}) - if err != nil { - return nil, fmt.Errorf("add site: please, contact to the site owner to get an invite token: %w", err) - } - - role = res.Role - } - - if err = sitesql.AddSite(conn, account, strings.Join(resp.Addresses, ","), in.Hostname, int64(role)); err != nil { - return nil, fmt.Errorf("add site: could not insert site in the database: %w", err) - } - - return &documents.SiteConfig{ - Hostname: in.Hostname, - Role: documents.Member_Role(role), - }, nil -} - -// RemoveSite removes locally a previously added site. -func (api *Server) RemoveSite(ctx context.Context, req *documents.RemoveSiteRequest) (*emptypb.Empty, error) { - empty := &emptypb.Empty{} - if req.Hostname == "" { - return empty, fmt.Errorf("empty hostname") - } - - conn, cancel, err := api.db.Conn(ctx) - if err != nil { - return empty, fmt.Errorf("Cannot connect to internal db") - } - defer cancel() - return empty, sitesql.RemoveSite(conn, req.Hostname) -} - -// ListSites lists all the added sites. -func (api *Server) ListSites(ctx context.Context, req *documents.ListSitesRequest) (*documents.ListSitesResponse, error) { - var s []*documents.SiteConfig - conn, cancel, err := api.db.Conn(ctx) - if err != nil { - return &documents.ListSitesResponse{}, fmt.Errorf("Cannot connect to internal db") - } - defer cancel() - sites, err := sitesql.ListSites(conn) - if err != nil { - return &documents.ListSitesResponse{}, fmt.Errorf("Could not list sites: %w", err) - } - for _, info := range sites { - s = append(s, &documents.SiteConfig{ - Hostname: info.SitesHostname, - Role: documents.Member_Role(info.SitesRole), - }) - } - return &documents.ListSitesResponse{ - Sites: s, - }, nil -} - -// ListWebPublicationRecords returns all the sites where a given document has been published to. -func (api *Server) ListWebPublicationRecords(ctx context.Context, req *documents.ListWebPublicationRecordsRequest) (*documents.ListWebPublicationRecordsResponse, error) { - var ret []*documents.WebPublicationRecord - conn, cancel, err := api.db.Conn(ctx) - if err != nil { - return &documents.ListWebPublicationRecordsResponse{}, fmt.Errorf("Cannot connect to internal db") - } - defer cancel() - sites, err := sitesql.ListSites(conn) - if err != nil { - return &documents.ListWebPublicationRecordsResponse{}, fmt.Errorf("Could not list sites: %w", err) - } - for _, siteInfo := range sites { - header := metadata.New(map[string]string{mttnet.TargetSiteHostnameHeader: siteInfo.SitesHostname}) - ctx = metadata.NewIncomingContext(ctx, header) // Usually, the headers are written by the client in the outgoing context and server receives them in the incoming. But here we are writing the server directly - - docs, err := api.RemoteCaller.ListWebPublications(ctx, &documents.ListWebPublicationsRequest{}) - if err != nil { - continue - } - for _, doc := range docs.Publications { - if req.DocumentId == doc.DocumentId && (req.Version == "" || req.Version == doc.Version) { - if doc.Hostname != siteInfo.SitesHostname { - return &documents.ListWebPublicationRecordsResponse{}, fmt.Errorf("found document [%s] in remote site [%s], but the site was added locally as [%s]", req.DocumentId, doc.Hostname, siteInfo.SitesHostname) - } - ret = append(ret, &documents.WebPublicationRecord{ - DocumentId: doc.DocumentId, - Version: doc.Version, - Hostname: doc.Hostname, - Path: doc.Path, - }) - } - } - } - return &documents.ListWebPublicationRecordsResponse{ - Publications: ret, - }, nil -} diff --git a/backend/daemon/api/entities/v1alpha/entities_test.go b/backend/daemon/api/entities/v1alpha/entities_test.go index 69a71e1c3c..c6a6146d2e 100644 --- a/backend/daemon/api/entities/v1alpha/entities_test.go +++ b/backend/daemon/api/entities/v1alpha/entities_test.go @@ -2,6 +2,7 @@ package entities import ( "context" + "fmt" "mintter/backend/core/coretest" daemon "mintter/backend/daemon/api/daemon/v1alpha" "mintter/backend/daemon/storage" @@ -9,6 +10,7 @@ import ( "mintter/backend/hyper" "mintter/backend/pkg/must" "mintter/backend/testutil" + "sync" "testing" "time" @@ -19,6 +21,53 @@ import ( var _ entities.EntitiesServer = (*Server)(nil) +type cache struct { + funcs []onceFunc +} + +type onceFunc struct { + once sync.Once + fn func() string + val string +} + +func (o *onceFunc) Do() string { + o.once.Do(func() { + fmt.Println("doing") + o.val = o.fn() + }) + return o.val +} + +func (c *cache) Q(fn func() string) int { + idx := len(c.funcs) + c.funcs = append(c.funcs, onceFunc{fn: fn}) + return idx +} + +func (c *cache) Query(idx int) string { + return c.funcs[idx].Do() +} + +var c cache + +var ( + qSay = c.Q(func() string { + return "say" + "foo" + "bar" + }) + + qHey = c.Q(func() string { + return fmt.Sprintf("hey %s %s", "foo", "bar") + }) +) + +func BenchmarkCache(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = c.Query(qSay) + _ = c.Query(qHey) + } +} + func TestEntityTimeline(t *testing.T) { t.Parallel() diff --git a/backend/daemon/api/groups/v1alpha/db.go b/backend/daemon/api/groups/v1alpha/db.go new file mode 100644 index 0000000000..3ea77325f2 --- /dev/null +++ b/backend/daemon/api/groups/v1alpha/db.go @@ -0,0 +1,145 @@ +package groups + +import ( + "context" + "fmt" + "mintter/backend/hyper" + "mintter/backend/hyper/hypersql" + "mintter/backend/pkg/dqb" + "time" + + "crawshaw.io/sqlite" + "crawshaw.io/sqlite/sqlitex" + "github.com/ipfs/go-cid" + "github.com/libp2p/go-libp2p/core/peer" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// DB is a database for storing sites. +type DB struct { + db *sqlitex.Pool +} + +// NewSQLiteDB creates a new DB backed by a SQLite connection pool. +func NewSQLiteDB(db *sqlitex.Pool) *DB { + return &DB{db: db} +} + +// RecordSiteSync updates the last sync time of a site. +func (db *DB) RecordSiteSync(ctx context.Context, baseURL string, pid peer.ID, now time.Time, ok bool) error { + conn, release, err := db.db.Conn(ctx) + if err != nil { + return err + } + defer release() + + nowts := now.Unix() + + if err := sqlitex.Exec(conn, qRecordSiteSync(), nil, pid.String(), nowts, ok, nowts, baseURL); err != nil { + return err + } + + if conn.Changes() == 0 { + return fmt.Errorf("site %s not found", baseURL) + } + + return nil +} + +var qRecordSiteSync = dqb.Str(` + UPDATE remote_sites SET + peer_id = :pid, + last_sync_time = :now, + last_ok_sync_time = iif(:ok, :now, last_ok_sync_time) + WHERE url = :url; +`) + +// ForEachRelatedBlob collects all the related blobs for a given group and calls fn on each CID. +func (db *DB) ForEachRelatedBlob(ctx context.Context, group hyper.EntityID, fn func(c cid.Cid) error) error { + conn, release, err := db.db.Conn(ctx) + if err != nil { + return err + } + defer release() + + gdb, err := hypersql.EntitiesLookupID(conn, string(group)) + if err != nil { + return err + } + if gdb.EntitiesID == 0 { + return status.Errorf(codes.NotFound, "group %s not found", group) + } + + return sqlitex.Exec(conn, qCollectBlobs(), func(stmt *sqlite.Stmt) error { + var ( + id int64 + codec int64 + multihash []byte + ) + stmt.Scan(&id, &codec, &multihash) + + c := cid.NewCidV1(uint64(codec), multihash) + return fn(c) + }, gdb.EntitiesID) +} + +var qCollectBlobs = dqb.Str(` + WITH RECURSIVE + group_blobs (blob) AS ( + SELECT blob + FROM changes + WHERE entity = :group + UNION + SELECT blob_links.target + FROM blob_links, group_blobs + WHERE blob_links.source = group_blobs.blob + ), + account_entities (entity) AS ( + SELECT DISTINCT accounts.entity + FROM group_blobs + JOIN changes ON changes.blob = group_blobs.blob + JOIN accounts ON accounts.entity = changes.entity + ), + account_blobs (blob) AS ( + SELECT changes.blob + FROM account_entities + JOIN changes ON changes.entity = account_entities.entity + UNION + SELECT blob_links.target + FROM account_blobs + JOIN blob_links ON blob_links.source = account_blobs.blob + ), + all_blobs (blob) AS ( + SELECT blob FROM group_blobs + UNION + SELECT blob FROM account_blobs + ) + SELECT + blobs.id AS id, + blobs.codec AS codec, + blobs.multihash AS multihash + FROM all_blobs + JOIN blobs ON blobs.id = all_blobs.blob + ORDER BY blobs.id ASC; +`) + +func (db *DB) QueryOne(ctx context.Context, sql string, args []any, outs []any) error { + conn, release, err := db.db.Conn(ctx) + if err != nil { + return err + } + defer release() + + var count int + + return sqlitex.Exec(conn, sql, func(stmt *sqlite.Stmt) error { + count++ + if count != 1 { + return fmt.Errorf("expected one row, but got more") + } + + stmt.Scan(outs...) + return nil + }, args...) +} diff --git a/backend/daemon/api/groups/v1alpha/groups.go b/backend/daemon/api/groups/v1alpha/groups.go index c79ee6546e..9679247d95 100644 --- a/backend/daemon/api/groups/v1alpha/groups.go +++ b/backend/daemon/api/groups/v1alpha/groups.go @@ -6,25 +6,30 @@ import ( "context" "encoding/json" "fmt" + "io" "mintter/backend/core" groups "mintter/backend/genproto/groups/v1alpha" - p2p "mintter/backend/genproto/p2p/v1alpha" "mintter/backend/hlc" "mintter/backend/hyper" "mintter/backend/hyper/hypersql" + "mintter/backend/ipfs" "mintter/backend/mttnet" - "mintter/backend/mttnet/sitesql" + "mintter/backend/pkg/dqb" "mintter/backend/pkg/errutil" "mintter/backend/pkg/future" "mintter/backend/pkg/maputil" + "net/http" + "net/url" "strings" "time" "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" "github.com/ipfs/go-cid" + "github.com/libp2p/go-libp2p/core/peer" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -32,18 +37,125 @@ import ( type Server struct { me *future.ReadOnly[core.Identity] blobs *hyper.Storage + db *DB node *future.ReadOnly[*mttnet.Node] } // NewServer creates a new groups server. -func NewServer(me *future.ReadOnly[core.Identity], blobs *hyper.Storage, node *future.ReadOnly[*mttnet.Node]) *Server { +func NewServer(me *future.ReadOnly[core.Identity], db *DB, blobs *hyper.Storage, node *future.ReadOnly[*mttnet.Node]) *Server { return &Server{ me: me, + db: db, blobs: blobs, node: node, } } +// StartPeriodicSync starts periodic sync of sites. +// It will block until the provided context is canceled. +func (srv *Server) StartPeriodicSync(ctx context.Context, warmup, interval time.Duration) error { + t := time.NewTimer(warmup) + defer t.Stop() + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-t.C: + + t.Reset(interval) + } + } +} + +var qGetSite = dqb.Str(` + SELECT + url, + peer_id, + group_id, + group_version, + last_sync_time, + last_ok_sync_time + FROM remote_sites + WHERE url = :url; +`) + +func (srv *Server) syncSite(ctx context.Context, siteURL string, interval time.Duration) error { + var ( + url string + peerID string + groupID string + groupVersion string + lastSyncTime int64 + lastSyncOkTime int64 + ) + if err := srv.db.QueryOne(ctx, qGetSite(), + []any{siteURL}, + []any{&url, &peerID, &groupID, &groupVersion, &lastSyncTime, &lastSyncOkTime}, + ); err != nil { + return err + } + + now := time.Now() + lastSync := time.Unix(lastSyncTime, 0) + + if now.Sub(lastSync) < interval { + return nil + } + + info, err := GetSiteInfoHTTP(ctx, nil, siteURL) + if err != nil { + return fmt.Errorf("failed to get site info: %w", err) + } + + if info.GroupId != groupID { + return fmt.Errorf("group ID mismatch: remote %s != local %s", info.GroupId, groupID) + } + + if info.GroupVersion == groupVersion { + return nil + } + + // otherwise do the sync + // get remote info + // check remote group id correspond with the local one + // get all blobs from site + // push all blobs to site + // get remote version and check if we have heads. If so => skip + // sync with site + // + + // TODO: record sync time in db + // if we don't have version heads - sync everything + // + + // n, err := srv.node.Await(ctx) + // if err != nil { + // return err + // } + + // ai, err := addrInfoFromProto(info.PeerInfo) + // if err != nil { + // return fmt.Errorf("failed to parse peer info: %w", err) + // } + + // // Using libp2p connect instead of mttnet connect to skip the handshake. + // // This way we never actually exchange the site's key delegation, so we won't be + // // syncing with sites that we don't care about using the regular syncing process. + // // + // // TODO(burdiyan): BAD! + // if err := n.Libp2p().Connect(ctx, ai); err != nil { + // return err + // } + + // // client, err := n.SiteClient(ctx, ai.ID) + // // if err != nil { + // // return err + // // } + + return nil +} + // CreateGroup creates a new group. func (srv *Server) CreateGroup(ctx context.Context, in *groups.CreateGroupRequest) (*groups.Group, error) { if in.Title == "" { @@ -77,10 +189,20 @@ func (srv *Server) CreateGroup(ctx context.Context, in *groups.CreateGroupReques return nil, status.Errorf(codes.Unimplemented, "adding members when creating a group is not implemented yet") } + if in.SiteSetupUrl != "" { + siteURL, err := srv.initSiteServer(ctx, in.SiteSetupUrl, eid) + if err != nil { + return nil, err + } + + patch["siteURL"] = siteURL + } + del, err := srv.getDelegation(ctx) if err != nil { return nil, err } + hb, err := e.CreateChange(ts, me.DeviceKey(), del, patch, hyper.WithAction("Create")) if err != nil { return nil, err @@ -90,7 +212,71 @@ func (srv *Server) CreateGroup(ctx context.Context, in *groups.CreateGroupReques return nil, err } - return groupToProto(srv.blobs, e) + return groupToProto(e) +} + +func (srv *Server) initSiteServer(ctx context.Context, setupURL string, groupID hyper.EntityID) (baseURL string, err error) { + n, err := srv.node.Await(ctx) + if err != nil { + return "", err + } + + { + u, err := url.Parse(setupURL) + if err != nil { + return "", fmt.Errorf("failed to parse setup URL %s: %w", setupURL, err) + } + + baseURL = (&url.URL{ + Scheme: u.Scheme, + Host: u.Host, + }).String() + } + + resp, err := GetSiteInfoHTTP(ctx, nil, baseURL) + if err != nil { + return "", fmt.Errorf("could not contact site at %s: %w", baseURL, err) + } + + ai, err := addrInfoFromProto(resp.PeerInfo) + if err != nil { + return "", err + } + + if err := n.Connect(ctx, ai); err != nil { + return "", fmt.Errorf("failed to connect to site via P2P: %w", err) + } + + c, err := n.SiteClient(ctx, ai.ID) + if err != nil { + return "", fmt.Errorf("could not get site rpc client: %w", err) + } + + if _, err := c.InitializeServer(ctx, &groups.InitializeServerRequest{ + Secret: setupURL, + GroupId: string(groupID), + }); err != nil { + return "", fmt.Errorf("could not publish group to site: %w", err) + } + + return baseURL, nil +} + +func addrInfoFromProto(in *groups.PeerInfo) (ai peer.AddrInfo, err error) { + pid, err := peer.Decode(in.PeerId) + if err != nil { + return ai, err + } + + addrs, err := ipfs.ParseMultiaddrs(in.Addrs) + if err != nil { + return ai, fmt.Errorf("failed to parse peer info addrs: %w", err) + } + + return peer.AddrInfo{ + ID: pid, + Addrs: addrs, + }, nil } // GetGroup gets a group. @@ -121,7 +307,7 @@ func (srv *Server) GetGroup(ctx context.Context, in *groups.GetGroupRequest) (*g e = v } - return groupToProto(srv.blobs, e) + return groupToProto(e) } // UpdateGroup updates a group. @@ -180,6 +366,15 @@ func (srv *Server) UpdateGroup(ctx context.Context, in *groups.UpdateGroupReques return nil, err } + if in.SiteSetupUrl != "" { + siteURL, err := srv.initSiteServer(ctx, in.SiteSetupUrl, eid) + if err != nil { + return nil, err + } + + patch["siteURL"] = siteURL + } + hb, err := e.CreateChange(e.NextTimestamp(), me.DeviceKey(), del, patch, hyper.WithAction("Update")) if err != nil { return nil, err @@ -189,7 +384,7 @@ func (srv *Server) UpdateGroup(ctx context.Context, in *groups.UpdateGroupReques return nil, err } - return groupToProto(srv.blobs, e) + return groupToProto(e) } // ListGroups lists groups. @@ -316,69 +511,6 @@ func (srv *Server) ListMembers(ctx context.Context, in *groups.ListMembersReques return resp, nil } -// GetSiteInfo gets information of a local site. -func (srv *Server) GetSiteInfo(ctx context.Context, in *groups.GetSiteInfoRequest) (*groups.GetSiteInfoResponse, error) { - ret := &groups.GetSiteInfoResponse{} - if err := srv.blobs.Query(ctx, func(conn *sqlite.Conn) error { - res, err := sitesql.GetSiteInfo(conn, in.Hostname) - if err != nil { - return fmt.Errorf("No site info available: %w", err) - } - ret.GroupId = res.EntitiesEID - if res.ServedSitesVersion != "" { - ret.Version = res.ServedSitesVersion - } else { - entity, err := srv.blobs.LoadEntity(ctx, hyper.EntityID(res.EntitiesEID)) - if err != nil { - return fmt.Errorf("could not get entity [%s]: %w", res.EntitiesEID, err) - } - ret.Version = entity.Version().String() - } - - ret.OwnerId = core.Principal(res.PublicKeysPrincipal).String() - return nil - }); err != nil { - return nil, err - } - return ret, nil -} - -// ConvertToSite converts a group into a site. P2P group will still work as usual after this call. -func (srv *Server) ConvertToSite(ctx context.Context, in *groups.ConvertToSiteRequest) (*groups.ConvertToSiteResponse, error) { - n, ok := srv.node.Get() - if !ok { - return nil, fmt.Errorf("node not ready yet") - } - - remoteHostname := strings.Split(in.Link, "/secret-invite/")[0] - - info, err := mttnet.GetSiteAddressFromHeaders(remoteHostname) - if err != nil { - return nil, fmt.Errorf("Could not get site [%s] info via http: %w", remoteHostname, err) - } - - if err := n.Connect(ctx, info); err != nil { - return nil, fmt.Errorf("failed to connect to site [%s] with peer info [%s]: %w", remoteHostname, info.String(), err) - } - client, err := n.Client(ctx, info.ID) - if err != nil { - return nil, fmt.Errorf("failed to get a p2p client with node [%s]: %w", info.ID.String(), err) - } - res, err := client.CreateSite(ctx, &p2p.CreateSiteRequest{ - Link: in.Link, - GroupId: in.GroupId, - Version: in.Version, - }) - if err != nil { - return nil, fmt.Errorf("Failed to create a remote site: %w", err) - } - - return &groups.ConvertToSiteResponse{ - OwnerId: res.OwnerId, - Hostname: remoteHostname, - }, nil -} - // ListDocumentGroups lists groups that a document belongs to. func (srv *Server) ListDocumentGroups(ctx context.Context, in *groups.ListDocumentGroupsRequest) (*groups.ListDocumentGroupsResponse, error) { if in.DocumentId == "" { @@ -541,7 +673,7 @@ func (srv *Server) ListAccountGroups(ctx context.Context, in *groups.ListAccount return resp, nil } -func groupToProto(blobs *hyper.Storage, e *hyper.Entity) (*groups.Group, error) { +func groupToProto(e *hyper.Entity) (*groups.Group, error) { createTime, ok := e.AppliedChanges()[0].Data.Patch["createTime"].(int) if !ok { return nil, fmt.Errorf("group entity doesn't have createTime field") @@ -559,6 +691,14 @@ func groupToProto(blobs *hyper.Storage, e *hyper.Entity) (*groups.Group, error) Version: e.Version().String(), UpdateTime: timestamppb.New(e.LastChangeTime().Time()), } + if v, ok := e.Get("siteURL"); ok { + vv, ok := v.(string) + if ok { + gpb.SiteInfo = &groups.Group_SiteInfo{ + BaseUrl: vv, + } + } + } { v, ok := e.Get("title") @@ -622,3 +762,45 @@ func (srv *Server) getDelegation(ctx context.Context) (cid.Cid, error) { return out, nil } + +// GetSiteInfoHTTP gets public information from a site. +// Users can pass nil HTTP client in which case the default global one will be used. +func GetSiteInfoHTTP(ctx context.Context, client *http.Client, siteURL string) (*groups.PublicSiteInfo, error) { + if client == nil { + client = http.DefaultClient + } + + fmt.Println(siteURL) + + if siteURL[len(siteURL)-1] == '/' { + return nil, fmt.Errorf("site URL must not have trailing slash: %s", siteURL) + } + + requestURL := siteURL + "/.well-known/hypermedia-site" + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil) + if err != nil { + return nil, fmt.Errorf("could not create request to well-known site: %w ", err) + } + + res, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("could not contact to provided site [%s]: %w ", requestURL, err) + } + defer res.Body.Close() + if res.StatusCode < 200 || res.StatusCode > 299 { + return nil, fmt.Errorf("site info url [%s] not working. Status code: %d", requestURL, res.StatusCode) + } + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("failed to read json body: %w", err) + } + + resp := &groups.PublicSiteInfo{} + if err := protojson.Unmarshal(data, resp); err != nil { + return nil, fmt.Errorf("failed to unmarshal JSON body: %w", err) + } + + return resp, nil +} diff --git a/backend/daemon/api/groups/v1alpha/groups_test.go b/backend/daemon/api/groups/v1alpha/groups_test.go index a70dbadde3..3f86c04ffe 100644 --- a/backend/daemon/api/groups/v1alpha/groups_test.go +++ b/backend/daemon/api/groups/v1alpha/groups_test.go @@ -552,7 +552,7 @@ func newTestSrv(t *testing.T, name string) *Server { bs := hyper.NewStorage(db, logging.New("mintter/hyper", "debug")) node := future.New[*mttnet.Node]() - srv := NewServer(fut.ReadOnly, bs, node.ReadOnly) + srv := NewServer(fut.ReadOnly, NewSQLiteDB(db), bs, node.ReadOnly) _, err := daemon.Register(context.Background(), bs, u.Account, u.Device.PublicKey, time.Now()) require.NoError(t, err) diff --git a/backend/daemon/api/register.go b/backend/daemon/api/register.go index a7595b71c3..79b5edfe6e 100644 --- a/backend/daemon/api/register.go +++ b/backend/daemon/api/register.go @@ -16,14 +16,12 @@ func (s Server) Register(srv *grpc.Server) { accounts.RegisterAccountsServer(srv, s.Accounts) daemon.RegisterDaemonServer(srv, s.Daemon) - documents.RegisterWebPublishingServer(srv, s.Documents) documents.RegisterContentGraphServer(srv, s.Documents) documents.RegisterDraftsServer(srv, s.Documents) documents.RegisterPublicationsServer(srv, s.Documents) // documents.RegisterCommentsServer(srv, s.Documents) documents.RegisterCommentsServer(srv, documents.UnimplementedCommentsServer{}) documents.RegisterChangesServer(srv, s.Documents) - documents.RegisterWebSiteServer(srv, s.Site) networking.RegisterNetworkingServer(srv, s.Networking) entities.RegisterEntitiesServer(srv, s.Entities) diff --git a/backend/daemon/daemon.go b/backend/daemon/daemon.go index 8a9b826b18..a924c1e599 100644 --- a/backend/daemon/daemon.go +++ b/backend/daemon/daemon.go @@ -4,23 +4,16 @@ package daemon import ( "context" - "encoding/json" "fmt" - "io/ioutil" "net" "net/http" - "os" - "runtime/debug" "strconv" - "strings" "time" "mintter/backend/config" "mintter/backend/core" "mintter/backend/daemon/api" "mintter/backend/daemon/storage" - daemon "mintter/backend/genproto/daemon/v1alpha" - "mintter/backend/graphql" "mintter/backend/hyper" "mintter/backend/ipfs" "mintter/backend/logging" @@ -30,10 +23,10 @@ import ( "mintter/backend/syncing" "mintter/backend/wallet" + groups "mintter/backend/genproto/groups/v1alpha" + "crawshaw.io/sqlite/sqlitex" - "github.com/99designs/gqlgen/graphql/playground" "github.com/gorilla/mux" - "github.com/improbable-eng/grpc-web/go/grpcweb" "github.com/libp2p/go-libp2p/core/crypto" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/collectors" @@ -42,12 +35,9 @@ import ( "go.opentelemetry.io/otel/sdk/trace" "go.uber.org/multierr" "go.uber.org/zap" - "golang.org/x/exp/slices" "golang.org/x/sync/errgroup" "google.golang.org/grpc" - "google.golang.org/grpc/codes" "google.golang.org/grpc/reflection" - "google.golang.org/grpc/status" ) func init() { @@ -88,31 +78,7 @@ type App struct { // futures might not be resolved yet. // // To shut down the app gracefully cancel the provided context and call Wait(). -func Load(ctx context.Context, cfg config.Config, grpcOpt ...grpc.ServerOption) (a *App, err error) { - var deviceKey crypto.PrivKey - if cfg.Identity.DeviceKeyPath != "" { - if _, err := os.Stat(cfg.Identity.DeviceKeyPath); err == nil { - bytes, err := ioutil.ReadFile(cfg.Identity.DeviceKeyPath) - if err != nil { - return nil, err - } - deviceKey, err = crypto.UnmarshalPrivateKey(bytes) - if err != nil { - return nil, err - } - } else { - return nil, err - } - } - r, err := initRepo(cfg, deviceKey) - if err != nil { - return nil, err - } - - return loadApp(ctx, cfg, r, grpcOpt...) -} - -func loadApp(ctx context.Context, cfg config.Config, r *storage.Dir, grpcOpt ...grpc.ServerOption) (a *App, err error) { +func Load(ctx context.Context, cfg config.Config, r *storage.Dir, extraOpts ...interface{}) (a *App, err error) { a = &App{ log: logging.New("mintter/daemon", "debug"), Storage: r, @@ -161,7 +127,7 @@ func loadApp(ctx context.Context, cfg config.Config, r *storage.Dir, grpcOpt ... me := a.Storage.Identity() - a.Net, err = initNetwork(&a.clean, a.g, me, cfg.P2P, a.DB, a.Blobs) + a.Net, err = initNetwork(&a.clean, a.g, me, cfg.P2P, a.DB, a.Blobs, extraOpts...) if err != nil { return nil, err } @@ -173,24 +139,17 @@ func loadApp(ctx context.Context, cfg config.Config, r *storage.Dir, grpcOpt ... a.Wallet = wallet.New(ctx, logging.New("mintter/wallet", "debug"), a.DB, a.Net, me, cfg.Lndhub.Mainnet) - a.GRPCServer, a.GRPCListener, a.RPC, err = initGRPC(ctx, cfg.GRPCPort, &a.clean, a.g, me, a.Storage, a.DB, a.Blobs, a.Net, a.Syncing, a.Wallet, cfg.Site, grpcOpt...) + extraHTTPHandlers := []GenericHandler{} + for _, extra := range extraOpts { + if httpHandler, ok := extra.(GenericHandler); ok { + extraHTTPHandlers = append(extraHTTPHandlers, httpHandler) + } + } + a.GRPCServer, a.GRPCListener, a.RPC, err = initGRPC(ctx, cfg.GRPC.Port, &a.clean, a.g, me, a.Storage, a.DB, a.Blobs, a.Net, a.Syncing, a.Wallet, extraOpts...) if err != nil { return nil, err } - if cfg.Identity.NoAccountWait { - res, err := a.RPC.Daemon.GenMnemonic(ctx, &daemon.GenMnemonicRequest{MnemonicsLength: 12}) - if err != nil { - return nil, fmt.Errorf("Cannot create automatic mnemonics: %w", err) - } - _, err = a.RPC.Daemon.Register(ctx, &daemon.RegisterRequest{Mnemonic: res.Mnemonic, Passphrase: ""}) - stat, ok := status.FromError(err) - - if !ok && stat.Code() != codes.AlreadyExists { - return nil, fmt.Errorf("Cannot register automatic account: %w", err) - } - } - fileManager := ipfs.NewManager(ctx, logging.New("mintter/ipfs", "debug")) // We can't use futures in ipfs.NewManager since we will incur in a @@ -205,7 +164,7 @@ func loadApp(ctx context.Context, cfg config.Config, r *storage.Dir, grpcOpt ... return fileManager.Start(n.Blobs().IPFSBlockstore(), n.Bitswap(), n.Provider()) }) - a.HTTPServer, a.HTTPListener, err = initHTTP(cfg.HTTPPort, a.GRPCServer, &a.clean, a.g, a.DB, a.Net, me, a.Wallet, a.RPC.Site, fileManager) + a.HTTPServer, a.HTTPListener, err = initHTTP(cfg.HTTP.Port, a.GRPCServer, &a.clean, a.g, a.Wallet, fileManager, extraHTTPHandlers...) if err != nil { return nil, err } @@ -221,7 +180,7 @@ func (a *App) setupLogging(ctx context.Context, cfg config.Config) { a.log.Info("DaemonStarted", zap.String("grpcListener", a.GRPCListener.Addr().String()), zap.String("httpListener", a.HTTPListener.Addr().String()), - zap.String("repoPath", cfg.RepoPath), + zap.String("dataDir", cfg.DataDir), ) n, err := a.Net.Await(ctx) @@ -257,12 +216,14 @@ func (a *App) Wait() error { return a.g.Wait() } -func initRepo(cfg config.Config, device crypto.PrivKey) (r *storage.Dir, err error) { +// InitRepo initializes the storage directory. +// Device can be nil in which case a random new device key will be generated. +func InitRepo(dataDir string, device crypto.PrivKey) (r *storage.Dir, err error) { log := logging.New("mintter/repo", "debug") if device == nil { - r, err = storage.New(cfg.RepoPath, log) + r, err = storage.New(dataDir, log) } else { - r, err = storage.NewWithDeviceKey(cfg.RepoPath, log, device) + r, err = storage.NewWithDeviceKey(dataDir, log, device) } if err != nil { return nil, fmt.Errorf("failed to init storage: %w", err) @@ -293,6 +254,7 @@ func initNetwork( cfg config.P2P, db *sqlitex.Pool, blobs *hyper.Storage, + extraServers ...interface{}, ) (*future.ReadOnly[*mttnet.Node], error) { f := future.New[*mttnet.Node]() @@ -314,7 +276,7 @@ func initNetwork( return err } - n, err := mttnet.New(cfg, db, blobs, id, logging.New("mintter/network", "debug")) + n, err := mttnet.New(cfg, db, blobs, id, logging.New("mintter/network", "debug"), extraServers...) if err != nil { return err } @@ -374,16 +336,20 @@ func initSyncing( return err } - svc := syncing.NewService(logging.New("mintter/syncing", "debug"), id, db, blobs, node.Bitswap(), node.Client, cfg.NoInbound) + svc := syncing.NewService(logging.New("mintter/syncing", "debug"), id, db, blobs, node.Bitswap(), node.Client) svc.SetWarmupDuration(cfg.WarmupDuration) svc.SetPeerSyncTimeout(cfg.TimeoutPerPeer) svc.SetSyncInterval(cfg.Interval) - g.Go(func() error { - err := svc.Start(ctx) + if cfg.Disabled { close(done) - return err - }) + } else { + g.Go(func() error { + err := svc.Start(ctx) + close(done) + return err + }) + } if err := f.Resolve(svc); err != nil { return err @@ -407,20 +373,32 @@ func initGRPC( node *future.ReadOnly[*mttnet.Node], sync *future.ReadOnly[*syncing.Service], wallet *wallet.Service, - cfg config.Site, - opts ...grpc.ServerOption, + extras ...interface{}, ) (srv *grpc.Server, lis net.Listener, rpc api.Server, err error) { lis, err = net.Listen("tcp", ":"+strconv.Itoa(port)) if err != nil { return } + opts := []grpc.ServerOption{} + for _, extra := range extras { + if opt, ok := extra.(grpc.ServerOption); ok { + opts = append(opts, opt) + } + } srv = grpc.NewServer(opts...) - rpc = api.New(ctx, repo, pool, blobs, node, sync, wallet, cfg) + rpc = api.New(ctx, repo, pool, blobs, node, sync, wallet) rpc.Register(srv) reflection.Register(srv) + for _, extra := range extras { + if extraServer, ok := extra.(groups.WebsiteServer); ok { + groups.RegisterWebsiteServer(srv, extraServer) + break + } + } + g.Go(func() error { return srv.Serve(lis) }) @@ -479,162 +457,40 @@ func setRoute(m *mux.Router, path string, isPrefix bool, h http.Handler) { } const ( - routePrefix = 1 << 1 - routeNav = 1 << 2 + // RoutePrefix exposes path prefix. + RoutePrefix = 1 << 1 + // RouteNav adds the path to a route nav. + RouteNav = 1 << 2 ) -type router struct { +// Router is a wrapper around mux that can build the navigation menu. +type Router struct { r *mux.Router nav []string } -func (r *router) Handle(path string, h http.Handler, mode int) { +// Handle a route. +func (r *Router) Handle(path string, h http.Handler, mode int) { h = instrumentHTTPHandler(h, path) - if mode&routePrefix != 0 { + if mode&RouteNav != 0 { r.r.PathPrefix(path).Handler(h) } else { r.r.Handle(path, h) } - if mode&routeNav != 0 { + if mode&RouteNav != 0 { r.nav = append(r.nav, path) } } -func (r *router) Index(w http.ResponseWriter, req *http.Request) { +func (r *Router) Index(w http.ResponseWriter, req *http.Request) { for _, route := range r.nav { fmt.Fprintf(w, `

%s

`, route, route) } } -func initHTTP( - port int, - rpc *grpc.Server, - clean *cleanup.Stack, - g *errgroup.Group, - db *sqlitex.Pool, - node *future.ReadOnly[*mttnet.Node], - me *future.ReadOnly[core.Identity], - wallet *wallet.Service, - wellKnownHandler http.Handler, - ipfsHandler ipfs.HTTPHandler, -) (srv *http.Server, lis net.Listener, err error) { - var h http.Handler - { - grpcWebHandler := grpcweb.WrapServer(rpc, grpcweb.WithOriginFunc(func(origin string) bool { - return true - })) - - router := router{r: mux.NewRouter()} - router.Handle("/debug/metrics", promhttp.Handler(), routeNav) - router.Handle("/debug/pprof", http.DefaultServeMux, routePrefix|routeNav) - router.Handle("/debug/vars", http.DefaultServeMux, routePrefix|routeNav) - router.Handle("/debug/grpc", grpcLogsHandler(), routeNav) - router.Handle("/debug/buildinfo", buildInfoHandler(), routeNav) - router.Handle("/graphql", corsMiddleware(graphql.Handler(wallet)), 0) - router.Handle("/playground", playground.Handler("GraphQL Playground", "/graphql"), routeNav) - router.Handle("/"+mttnet.WellKnownPath, wellKnownHandler, routeNav) - router.Handle(ipfs.IPFSRootRoute+ipfs.UploadRoute, http.HandlerFunc(ipfsHandler.UploadFile), 0) - router.Handle(ipfs.IPFSRootRoute+ipfs.GetRoute, http.HandlerFunc(ipfsHandler.GetFile), 0) - - router.r.MatcherFunc(mux.MatcherFunc(func(r *http.Request, match *mux.RouteMatch) bool { - return grpcWebHandler.IsAcceptableGrpcCorsRequest(r) || grpcWebHandler.IsGrpcWebRequest(r) - })).Handler(grpcWebHandler) - - router.Handle("/", http.HandlerFunc(router.Index), 0) - - h = router.r - } - - srv = &http.Server{ - Addr: ":" + strconv.Itoa(port), - ReadHeaderTimeout: 5 * time.Second, - // WriteTimeout: 10 * time.Second, - IdleTimeout: 20 * time.Second, - Handler: h, - } - - lis, err = net.Listen("tcp", srv.Addr) - if err != nil { - return - } - - g.Go(func() error { - err := srv.Serve(lis) - if err == http.ErrServerClosed { - return nil - } - return err - }) - - clean.AddErrFunc(func() error { - return srv.Shutdown(context.Background()) - }) - - return -} - -// corsMiddleware allows different host/origins. -func corsMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // allow cross domain AJAX requests - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") - next.ServeHTTP(w, r) - }) -} - // WithMiddleware generates an grpc option with the given middleware. func WithMiddleware(i grpc.UnaryServerInterceptor) grpc.ServerOption { return grpc.UnaryInterceptor(i) } - -// GwEssentials is a middleware to restrict incoming grpc calls to bare minimum for the gateway to work. -func GwEssentials(ctx context.Context, - req interface{}, - info *grpc.UnaryServerInfo, - handler grpc.UnaryHandler) (interface{}, error) { - methodSplitted := strings.Split(info.FullMethod, "/") - if len(methodSplitted) < 2 || (strings.ToLower(methodSplitted[len(methodSplitted)-1]) != "getpublication" && - strings.ToLower(methodSplitted[len(methodSplitted)-1]) != "listcitations" && - strings.ToLower(methodSplitted[len(methodSplitted)-1]) != "getaccount") { - return nil, fmt.Errorf("method: %s not allowed", info.FullMethod) - } - - // Calls the handler - h, err := handler(ctx, req) - - return h, err -} - -func buildInfoHandler() http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - info, ok := debug.ReadBuildInfo() - if !ok { - http.Error(w, "doesn't support build info", http.StatusExpectationFailed) - return - } - - // Don't want to show information about all the dependencies. - info.Deps = nil - - // Want to support text and json. - wantJSON := slices.Contains(r.Header.Values("Accept"), "application/json") || - r.URL.Query().Get("format") == "json" - - if wantJSON { - w.Header().Set("Content-Type", "application/json") - - enc := json.NewEncoder(w) - enc.SetIndent("", " ") - - if err := enc.Encode(info); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - } else { - w.Header().Set("Content-Type", "text/plain") - fmt.Fprint(w, info.String()) - } - }) -} diff --git a/backend/daemon/daemon_e2e_test.go b/backend/daemon/daemon_e2e_test.go index c1820bb1c0..f849b75b53 100644 --- a/backend/daemon/daemon_e2e_test.go +++ b/backend/daemon/daemon_e2e_test.go @@ -2,10 +2,7 @@ package daemon import ( "context" - "io/ioutil" - "mintter/backend/config" "mintter/backend/core" - "mintter/backend/core/coretest" accounts "mintter/backend/genproto/accounts/v1alpha" daemon "mintter/backend/genproto/daemon/v1alpha" documents "mintter/backend/genproto/documents/v1alpha" @@ -24,7 +21,6 @@ import ( "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/metadata" "google.golang.org/protobuf/proto" ) @@ -138,289 +134,6 @@ func TestAPIGetRemotePublication(t *testing.T) { testutil.ProtoEqual(t, publishedDocument, remotePublication, "remote publication doesn't match") } -func TestSite(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - - t.Cleanup(func() { - cancel() - }) - - owner := makeTestApp(t, "alice", makeTestConfig(t), true) - editor := makeTestApp(t, "bob", makeTestConfig(t), true) - editorFriend := makeTestApp(t, "alice-2", makeTestConfig(t), true) - reader := makeTestApp(t, "david", makeTestConfig(t), true) - - siteCfg := makeTestConfig(t) - siteCfg.Site.Hostname = "http://127.0.0.1:59011" - siteCfg.HTTPPort = 59011 - siteCfg.GRPCPort = mttnet.GRPCPort - siteCfg.Identity.NoAccountWait = true - siteCfg.Site.NoAuth = false - siteCfg.Site.Title = "initial Site Title" - siteCfg.Site.OwnerID = owner.Storage.Identity().MustGet().Account().String() - siteCfg.P2P.NoListing = true - siteCfg.Syncing.NoInbound = true - - site := makeTestApp(t, "carol", siteCfg, false) - time.Sleep(500 * time.Millisecond) - - newSite, err := owner.RPC.Documents.AddSite(ctx, &documents.AddSiteRequest{Hostname: siteCfg.Site.Hostname}) - require.NoError(t, err) - require.Equal(t, siteCfg.Site.Hostname, newSite.Hostname) - require.Equal(t, documents.Member_OWNER, newSite.Role) - - _, err = owner.RPC.Documents.AddSite(ctx, &documents.AddSiteRequest{Hostname: siteCfg.Site.Hostname}) - require.Error(t, err, "adding the same site twice must fail") - - // The reader connects to the site via p2p only - _, err = reader.RPC.Networking.Connect(ctx, &networking.ConnectRequest{Addrs: getAddrs(t, site)}) - require.NoError(t, err) - - // The editor and his friend connect to each other - _, err = editorFriend.RPC.Networking.Connect(ctx, &networking.ConnectRequest{Addrs: getAddrs(t, editor)}) - require.NoError(t, err) - - // Adding a site as an editor without token should fail. - _, err = editor.RPC.Documents.AddSite(ctx, &documents.AddSiteRequest{Hostname: siteCfg.Site.Hostname}) - require.Error(t, err) - - // Generate a token for the editor. - header := metadata.New(map[string]string{string(mttnet.TargetSiteHostnameHeader): siteCfg.Site.Hostname}) - ctxWithHeaders := metadata.NewIncomingContext(ctx, header) // Typically, the headers are written by the client in the outgoing context and server receives them in the incoming. But here we are writing the server directly - address := "" - for _, ma := range site.Net.MustGet().AddrInfo().Addrs { - address += " " + ma.String() - } - ctxWithHeaders = context.WithValue(ctxWithHeaders, mttnet.TargetSiteHostnameHeader, site.Storage.Identity().MustGet().Account().String()) - token, err := owner.RPC.Site.CreateInviteToken(ctxWithHeaders, &documents.CreateInviteTokenRequest{Role: documents.Member_EDITOR}) - require.NoError(t, err) - - // Adding a site as an editor with invite token should succeed. - editorSite, err := editor.RPC.Documents.AddSite(ctx, &documents.AddSiteRequest{Hostname: siteCfg.Site.Hostname, InviteToken: token.Token}) - require.NoError(t, err) - require.Equal(t, siteCfg.Site.Hostname, editorSite.Hostname) - require.Equal(t, documents.Member_EDITOR, editorSite.Role) - - // Get initial site info. - siteInfo, err := owner.RPC.Site.GetSiteInfo(ctxWithHeaders, &documents.GetSiteInfoRequest{}) - require.NoError(t, err) - require.Equal(t, "", siteInfo.Description) - require.Equal(t, siteCfg.Site.Hostname, siteInfo.Hostname) - require.Equal(t, siteCfg.Site.OwnerID, siteInfo.Owner) - require.Equal(t, owner.Storage.Identity().MustGet().Account().String(), siteInfo.Owner) - require.Equal(t, siteCfg.Site.Title, siteInfo.Title) - siteAcc, err := site.RPC.Accounts.GetAccount(ctx, &accounts.GetAccountRequest{}) - require.NoError(t, err) - require.Equal(t, "Mintter Site", siteAcc.Profile.Bio) - require.Equal(t, siteCfg.Site.Title, siteAcc.Profile.Alias) - - // Change site info by the editor should fail - const newTitle = "new title" - const newDescription = " new brief description" - _, err = editor.RPC.Site.UpdateSiteInfo(ctxWithHeaders, &documents.UpdateSiteInfoRequest{Title: newTitle, Description: newDescription}) - if siteCfg.Site.NoAuth { - require.NoError(t, err) - } else { - require.Error(t, err) - } - - // Change site info by the owner shouldn't fail - siteInfo, err = owner.RPC.Site.UpdateSiteInfo(ctxWithHeaders, &documents.UpdateSiteInfoRequest{Title: newTitle, Description: newDescription}) - require.NoError(t, err) - require.Equal(t, newDescription, siteInfo.Description) - require.Equal(t, siteCfg.Site.Hostname, siteInfo.Hostname) - require.Equal(t, siteCfg.Site.OwnerID, siteInfo.Owner) - require.Equal(t, owner.Storage.Identity().MustGet().Account().String(), siteInfo.Owner) - require.Equal(t, newTitle, siteInfo.Title) - siteAcc, err = site.RPC.Accounts.GetAccount(ctx, &accounts.GetAccountRequest{}) - require.NoError(t, err) - require.Equal(t, newDescription, siteAcc.Profile.Bio) - require.Equal(t, newTitle, siteAcc.Profile.Alias) - - // Share a document. - sharedDocument := publishDocument(t, ctx, editor) - _, err = editor.RPC.Daemon.ForceSync(ctx, &daemon.ForceSyncRequest{}) - require.NoError(t, err) - _, err = editorFriend.RPC.Daemon.ForceSync(ctx, &daemon.ForceSyncRequest{}) - require.NoError(t, err) - - time.Sleep(500 * time.Millisecond) // Sleeping just to make sure it has time to propagate - - publicationList, err := site.RPC.Documents.ListPublications(ctx, &documents.ListPublicationsRequest{}) - require.NoError(t, err) - require.Len(t, publicationList.Publications, 0, "site must not sync documents with regular periodic sync") - - publicationList, err = editorFriend.RPC.Documents.ListPublications(ctx, &documents.ListPublicationsRequest{}) - require.NoError(t, err) - require.Len(t, publicationList.Publications, 1, "editor friend must have synced the document with the editor") - require.Equal(t, sharedDocument.Version, publicationList.Publications[0].Version) - require.Equal(t, sharedDocument.Document.Author, publicationList.Publications[0].Document.Author) - require.Equal(t, sharedDocument.Document.Id, publicationList.Publications[0].Document.Id) - - const indexPath = "/" - - _, err = editor.RPC.Site.PublishDocument(ctxWithHeaders, &documents.PublishDocumentRequest{ - DocumentId: sharedDocument.Document.Id, - Version: sharedDocument.Version, - Path: indexPath, - }) - require.NoError(t, err) - - // Site should have the document - publicationList, err = site.RPC.Documents.ListPublications(ctx, &documents.ListPublicationsRequest{}) - require.NoError(t, err) - require.Len(t, publicationList.Publications, 1) - require.Equal(t, sharedDocument.Version, publicationList.Publications[0].Version) - require.Equal(t, sharedDocument.Document.Author, publicationList.Publications[0].Document.Author) - require.Equal(t, sharedDocument.Document.Id, publicationList.Publications[0].Document.Id) - - // And owner should see it as well - _, err = site.RPC.Daemon.ForceSync(ctx, &daemon.ForceSyncRequest{}) - require.NoError(t, err) - _, err = owner.RPC.Daemon.ForceSync(ctx, &daemon.ForceSyncRequest{}) - require.NoError(t, err) - time.Sleep(500 * time.Millisecond) // Sleeping just to make sure it has time to propagate - - publicationList, err = owner.RPC.Documents.ListPublications(ctx, &documents.ListPublicationsRequest{}) - require.NoError(t, err) - require.Len(t, publicationList.Publications, 1) - require.Equal(t, sharedDocument.Version, publicationList.Publications[0].Version) - require.Equal(t, sharedDocument.Document.Author, publicationList.Publications[0].Document.Author) - require.Equal(t, sharedDocument.Document.Id, publicationList.Publications[0].Document.Id) - - // But the reader should not have it since its only connected to the site - publicationList, err = reader.RPC.Documents.ListPublications(ctx, &documents.ListPublicationsRequest{}) - require.NoError(t, err) - require.Len(t, publicationList.Publications, 0) - - // Even if he syncs, since NoListing = true site wont sync anything with non members - _, err = reader.RPC.Daemon.ForceSync(ctx, &daemon.ForceSyncRequest{}) - require.NoError(t, err) - time.Sleep(500 * time.Millisecond) // Sleeping just to make sure it has time to propagate - publicationList, err = reader.RPC.Documents.ListPublications(ctx, &documents.ListPublicationsRequest{}) - require.NoError(t, err) - require.Len(t, publicationList.Publications, 0) - - // Owner should view it in the site as published - sitePublications, err := owner.RPC.Site.ListWebPublications(ctxWithHeaders, &documents.ListWebPublicationsRequest{}) - require.NoError(t, err) - require.Len(t, sitePublications.Publications, 1) - require.Equal(t, sharedDocument.Version, sitePublications.Publications[0].Version) - require.Equal(t, siteCfg.Site.Hostname, sitePublications.Publications[0].Hostname) - require.Equal(t, indexPath, sitePublications.Publications[0].Path) - require.Equal(t, sharedDocument.Document.Id, sitePublications.Publications[0].DocumentId) - - // publish same doc to another path - const anotherPath = "another" - _, err = editor.RPC.Site.PublishDocument(ctxWithHeaders, &documents.PublishDocumentRequest{ - DocumentId: sharedDocument.Document.Id, - Version: sharedDocument.Version, - Path: anotherPath, - }) - require.Error(t, err, "must fail to publish same web publication with a different path") - - // publish a different version to another path - const anotherTitle = "New Document title leading to a new version" - newVersion := updateDocumenTitle(t, ctx, owner, sharedDocument.Document.Id, anotherTitle) - require.Equal(t, sharedDocument.Document.Id, newVersion.Document.Id) - - _, err = editor.RPC.Site.PublishDocument(ctxWithHeaders, &documents.PublishDocumentRequest{ - DocumentId: newVersion.Document.Id, - Version: newVersion.Version, - Path: anotherPath, - }) - require.Error(t, err, "must fail to publish web publication with a different version to a different path") - - // publish different version in same path should update the old one - _, err = editor.RPC.Site.PublishDocument(ctxWithHeaders, &documents.PublishDocumentRequest{ - DocumentId: newVersion.Document.Id, - Version: newVersion.Version, - Path: indexPath, - }) - require.Error(t, err, "must fail because editor doesn't have the document") // the editor does not have it, the owner does - - // Get the document from the network, and then try to republish. - wantedDoc, err := editor.RPC.Documents.GetPublication(ctx, &documents.GetPublicationRequest{ - DocumentId: newVersion.Document.Id, - Version: newVersion.Version, - }) - require.NoError(t, err) - require.Equal(t, newVersion.Version, wantedDoc.Version) - require.Equal(t, newVersion.Document.Id, wantedDoc.Document.Id) - - // Now republish - _, err = editor.RPC.Site.PublishDocument(ctxWithHeaders, &documents.PublishDocumentRequest{ - DocumentId: newVersion.Document.Id, - Version: newVersion.Version, - Path: indexPath, - }) - require.NoError(t, err) - doc, err := owner.RPC.Site.GetPath(ctxWithHeaders, &documents.GetPathRequest{Path: indexPath}) - require.NoError(t, err) - require.Equal(t, newVersion.Version, doc.Publication.Version) - require.Equal(t, anotherTitle, doc.Publication.Document.Title) - - // Different author changes the version and republishes to the same path - const anotherAuthorTitle = "Is this a change in authorship? Nope" - noNewAuthor := updateDocumenTitle(t, ctx, editor, newVersion.Document.Id, anotherAuthorTitle) - require.Equal(t, sharedDocument.Document.Author, noNewAuthor.Document.Author) - require.Equal(t, sharedDocument.Document.Id, noNewAuthor.Document.Id) - _, err = editor.RPC.Site.PublishDocument(ctxWithHeaders, &documents.PublishDocumentRequest{ - DocumentId: noNewAuthor.Document.Id, - Version: noNewAuthor.Version, - Path: indexPath, - }) - require.NoError(t, err) - - // Publish another document (owner) and give no time to sync and get it via getpublication in the editor. - newDocument := publishDocument(t, ctx, owner) - require.NoError(t, err) - _, err = owner.RPC.Site.PublishDocument(ctxWithHeaders, &documents.PublishDocumentRequest{ - DocumentId: newDocument.Document.Id, - Version: newDocument.Version, - Path: anotherPath, - }) - require.NoError(t, err) - sitePublications, err = editor.RPC.Site.ListWebPublications(ctxWithHeaders, &documents.ListWebPublicationsRequest{}) - require.NoError(t, err) - require.Len(t, sitePublications.Publications, 2) - - // Unpublish a document we haven't written should fail - _, err = editor.RPC.Site.UnpublishDocument(ctxWithHeaders, &documents.UnpublishDocumentRequest{ - DocumentId: newDocument.Document.Id, - }) - require.Error(t, err) - - // But the owner can unpublish - _, err = owner.RPC.Site.UnpublishDocument(ctxWithHeaders, &documents.UnpublishDocumentRequest{ - DocumentId: newDocument.Document.Id, - }) - require.NoError(t, err) - sitePublications, err = editor.RPC.Site.ListWebPublications(ctxWithHeaders, &documents.ListWebPublicationsRequest{}) - require.NoError(t, err) - require.Len(t, sitePublications.Publications, 1) - require.Equal(t, noNewAuthor.Version, sitePublications.Publications[0].Version) - require.Equal(t, indexPath, sitePublications.Publications[0].Path) - require.Equal(t, noNewAuthor.Document.Id, sitePublications.Publications[0].DocumentId) - - // Publish the previous shared document to the site on a blank path becomes unlisted. but same ID fails - _, err = editor.RPC.Site.PublishDocument(ctxWithHeaders, &documents.PublishDocumentRequest{ - DocumentId: sharedDocument.Document.Id, - Version: sharedDocument.Version, - }) - require.Error(t, err) - _, err = editor.RPC.Site.PublishDocument(ctxWithHeaders, &documents.PublishDocumentRequest{ - DocumentId: newDocument.Document.Id, - Version: newDocument.Version, - }) - require.NoError(t, err) - sitePublications, err = editor.RPC.Site.ListWebPublications(ctxWithHeaders, &documents.ListWebPublicationsRequest{}) - require.NoError(t, err) - require.Len(t, sitePublications.Publications, 2) - _, err = editor.RPC.Site.GetPath(ctxWithHeaders, &documents.GetPathRequest{}) - require.Error(t, err) -} - func TestTrustedChanges(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) @@ -499,74 +212,6 @@ func TestTrustedChanges(t *testing.T) { require.Equal(t, newVersion.Document.Id, publicationList.Publications[0].Document.Id) } -func TestGateway(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) - - gwConf := makeTestConfig(t) - gwConf.P2P.NoListing = true - - f, err := ioutil.TempFile("", "device.key") - require.NoError(t, err) - t.Cleanup(func() { - f.Close() - }) - - gwConf.Identity.DeviceKeyPath = f.Name() - - var deviceKeyBytes = []byte{8, 1, 18, 64, 213, 180, 8, 59, 161, 75, 15, 92, 212, 94, 225, 82, 81, 11, 32, 200, 62, 46, 190, 105, 121, 14, 176, 107, 195, 113, 153, 176, 198, 163, 215, 226, 79, 46, 215, 228, 133, 153, 14, 142, 52, 115, 21, 73, 202, 121, 204, 223, 53, 117, 164, 225, 248, 106, 231, 151, 180, 246, 107, 137, 227, 212, 98, 140} - bytes, err := f.Write(deviceKeyBytes) - require.NoError(t, err) - require.Equal(t, len(deviceKeyBytes), bytes) - - gw, err := Load(ctx, gwConf, WithMiddleware(GwEssentials)) - require.NoError(t, err) - t.Cleanup(func() { - cancel() - require.Equal(t, context.Canceled, gw.Wait()) - }) - - const mnemonicWords = 12 - mnemonic, err := core.NewBIP39Mnemonic(mnemonicWords) - require.NoError(t, err) - - _, err = gw.RPC.Daemon.Register(ctx, &daemon.RegisterRequest{ - Mnemonic: mnemonic, - Passphrase: "", - }) - require.NoError(t, err) - _, err = gw.Net.Await(ctx) - require.NoError(t, err) - - _, err = gw.Storage.Identity().Await(ctx) - require.NoError(t, err) - // Create new document so the gateway owns at least 1. This one must not be transferred to the requester, since the - // gateway only syncs in one direction (in order not to flood requesters with documents from everybody) - publishDocument(t, ctx, gw) - res, err := gw.RPC.Documents.ListPublications(ctx, &documents.ListPublicationsRequest{}) - require.NoError(t, err) - require.Equal(t, 1, len(res.Publications)) - // Create the publication that will be gotten by the gateway - _, publishedDocument, publisher := makeRemotePublication(t, ctx, gw) - remotePublication, err := gw.RPC.Documents.GetPublication(ctx, &documents.GetPublicationRequest{DocumentId: publishedDocument.Document.Id}) - require.NoError(t, err) - testutil.ProtoEqual(t, publishedDocument, remotePublication, "remote publication doesn't match") - // Gateway now should have two publications, the one created by itself and the one gotten from publisher - pubsGw, err := gw.RPC.Documents.ListPublications(ctx, &documents.ListPublicationsRequest{}) - require.NoError(t, err) - require.Equal(t, 2, len(pubsGw.Publications)) - pubPublisher, err := publisher.RPC.Documents.ListPublications(ctx, &documents.ListPublicationsRequest{}) - require.NoError(t, err) - require.Equal(t, 1, len(pubPublisher.Publications)) - // We force the publisher to sync all the content to see if it does not get the gateway's own article. - _, err = publisher.RPC.Daemon.ForceSync(ctx, &daemon.ForceSyncRequest{}) - require.NoError(t, err) - time.Sleep(time.Second) - pubPublisher, err = publisher.RPC.Documents.ListPublications(ctx, &documents.ListPublicationsRequest{}) - require.NoError(t, err) - require.Equal(t, 1, len(pubPublisher.Publications)) -} - func TestBug_SyncHangs(t *testing.T) { // See: https://github.com/mintterteam/mintter/issues/712. t.Parallel() @@ -885,43 +530,6 @@ func getAddrs(t *testing.T, a *App) []string { return mttnet.AddrInfoToStrings(a.Net.MustGet().AddrInfo()) } -func makeTestApp(t *testing.T, name string, cfg config.Config, register bool) *App { - ctx, cancel := context.WithCancel(context.Background()) - - u := coretest.NewTester(name) - - repo, err := initRepo(cfg, u.Device.Wrapped()) - require.NoError(t, err) - - app, err := loadApp(ctx, cfg, repo) - require.NoError(t, err) - t.Cleanup(func() { - cancel() - require.Equal(t, context.Canceled, app.Wait()) - }) - - if register { - err = app.RPC.Daemon.RegisterAccount(ctx, u.Account) - require.NoError(t, err) - - _, err = app.Net.Await(ctx) - require.NoError(t, err) - - _, err = app.Storage.Identity().Await(ctx) - require.NoError(t, err) - - prof := &accounts.Profile{ - Alias: name, - Bio: name + " bio", - } - acc, err := app.RPC.Accounts.UpdateProfile(ctx, prof) - require.NoError(t, err) - testutil.ProtoEqual(t, prof, acc.Profile, "profile update must return full profile") - } - - return app -} - func makeRemotePublication(t *testing.T, ctx context.Context, dhtProvider *App) (*App, *documents.Publication, *App) { var publisher *App { @@ -989,16 +597,3 @@ func updateDocumenTitle(t *testing.T, ctx context.Context, publisher *App, docID require.NoError(t, err) return published } - -func makeTestConfig(t *testing.T) config.Config { - cfg := config.Default() - - cfg.HTTPPort = 0 - cfg.GRPCPort = 0 - cfg.RepoPath = testutil.MakeRepoPath(t) - cfg.P2P.Port = 0 - cfg.P2P.BootstrapPeers = nil - cfg.P2P.NoRelay = true - cfg.P2P.NoMetrics = true - return cfg -} diff --git a/backend/daemon/daemon_queries_test.go b/backend/daemon/daemon_queries_test.go new file mode 100644 index 0000000000..0974b4e802 --- /dev/null +++ b/backend/daemon/daemon_queries_test.go @@ -0,0 +1,24 @@ +package daemon + +import ( + "mintter/backend/daemon/storage" + "mintter/backend/pkg/dqb" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDBQueries(t *testing.T) { + // This test is here because this is the most top-level package which we know + // imports all other packages that might have database queries. So all these + // queries would have been registered with the global query store in the dqb package. + // This test makes sure all queries are valid and use correct table and column names. + + t.Parallel() + + db, err := storage.OpenSQLite("file::memory:?mode=memory", 0, 1) + require.NoError(t, err) + defer db.Close() + require.NoError(t, storage.InitSQLiteSchema(db)) + require.NoError(t, dqb.GlobalQueries.Test(db)) +} diff --git a/backend/daemon/daemon_testing.go b/backend/daemon/daemon_testing.go new file mode 100644 index 0000000000..ab23794f61 --- /dev/null +++ b/backend/daemon/daemon_testing.go @@ -0,0 +1,72 @@ +package daemon + +import ( + "context" + "mintter/backend/config" + "mintter/backend/core/coretest" + accounts "mintter/backend/daemon/api/accounts/v1alpha" + "mintter/backend/testutil" + "testing" + + "github.com/stretchr/testify/require" +) + +// MakeTestApp creates a new daemon app for testing. +func MakeTestApp(t *testing.T, name string, cfg config.Config, register bool) *App { + return makeTestApp(t, name, cfg, register) +} + +// MakeTestConfig creates a new default config for testing. +func MakeTestConfig(t *testing.T) config.Config { + return makeTestConfig(t) +} + +func makeTestApp(t *testing.T, name string, cfg config.Config, register bool) *App { + ctx, cancel := context.WithCancel(context.Background()) + + u := coretest.NewTester(name) + + repo, err := InitRepo(cfg.Base.DataDir, u.Device.Wrapped()) + require.NoError(t, err) + + app, err := Load(ctx, cfg, repo) + require.NoError(t, err) + t.Cleanup(func() { + cancel() + require.Equal(t, context.Canceled, app.Wait()) + }) + + if register { + err = app.RPC.Daemon.RegisterAccount(ctx, u.Account) + require.NoError(t, err) + + _, err = app.Net.Await(ctx) + require.NoError(t, err) + + _, err = app.Storage.Identity().Await(ctx) + require.NoError(t, err) + + prof := &accounts.Profile{ + Alias: name, + Bio: name + " bio", + } + acc, err := app.RPC.Accounts.UpdateProfile(ctx, prof) + require.NoError(t, err) + testutil.ProtoEqual(t, prof, acc.Profile, "profile update must return full profile") + } + + return app +} + +func makeTestConfig(t *testing.T) config.Config { + cfg := config.Default() + + cfg.HTTP.Port = 0 + cfg.GRPC.Port = 0 + cfg.Base.DataDir = testutil.MakeRepoPath(t) + cfg.P2P.Port = 0 + cfg.P2P.BootstrapPeers = nil + cfg.P2P.NoRelay = true + cfg.P2P.NoMetrics = true + return cfg +} diff --git a/backend/daemon/http.go b/backend/daemon/http.go new file mode 100644 index 0000000000..00fadfd4ef --- /dev/null +++ b/backend/daemon/http.go @@ -0,0 +1,156 @@ +package daemon + +import ( + "context" + "encoding/json" + "fmt" + "mintter/backend/graphql" + "mintter/backend/ipfs" + "mintter/backend/pkg/cleanup" + "mintter/backend/wallet" + "net" + "net/http" + "runtime/debug" + "strconv" + "time" + + "github.com/99designs/gqlgen/graphql/playground" + "github.com/gorilla/mux" + "github.com/improbable-eng/grpc-web/go/grpcweb" + "github.com/prometheus/client_golang/prometheus/promhttp" + "golang.org/x/exp/slices" + "golang.org/x/sync/errgroup" + "google.golang.org/grpc" +) + +// GenericHandler is to be called bay anyone wanting to register a +// new http handler. +type GenericHandler struct { + // Path where the endpoint will be hosted. + Path string + // HTTP handler. + Handler http.Handler + // RoutePrefix | RouteNav. + Mode int +} + +// setupGraphQLHandlers sets up the GraphQL endpoints. +func setupGraphQLHandlers(r *Router, wallet *wallet.Service) { + r.Handle("/graphql", corsMiddleware(graphql.Handler(wallet)), 0) + r.Handle("/playground", playground.Handler("GraphQL Playground", "/graphql"), RouteNav) +} + +// setupIPFSFileHandlers sets up the IPFS file endpoints for uploading and getting files. +func setupIPFSFileHandlers(r *Router, h ipfs.HTTPHandler) { + r.Handle(ipfs.IPFSRootRoute+ipfs.UploadRoute, http.HandlerFunc(h.UploadFile), 0) + r.Handle(ipfs.IPFSRootRoute+ipfs.GetRoute, http.HandlerFunc(h.GetFile), 0) +} + +// setupDebugHandlers sets up the debug endpoints. +func setupDebugHandlers(r *Router) { + r.Handle("/debug/metrics", promhttp.Handler(), RouteNav) + r.Handle("/debug/pprof", http.DefaultServeMux, RoutePrefix|RouteNav) + r.Handle("/debug/vars", http.DefaultServeMux, RoutePrefix|RouteNav) + r.Handle("/debug/grpc", grpcLogsHandler(), RouteNav) + r.Handle("/debug/buildinfo", buildInfoHandler(), RouteNav) +} + +// setupGRPCWebHandler sets up the gRPC-Web handler. +func setupGRPCWebHandler(r *Router, rpc *grpc.Server) { + grpcWebHandler := grpcweb.WrapServer(rpc, grpcweb.WithOriginFunc(func(origin string) bool { + return true + })) + + r.r.MatcherFunc(mux.MatcherFunc(func(r *http.Request, match *mux.RouteMatch) bool { + return grpcWebHandler.IsAcceptableGrpcCorsRequest(r) || grpcWebHandler.IsGrpcWebRequest(r) + })).Handler(grpcWebHandler) +} + +func initHTTP( + port int, + rpc *grpc.Server, + clean *cleanup.Stack, + g *errgroup.Group, + wallet *wallet.Service, + ipfsHandler ipfs.HTTPHandler, + extraHandlers ...GenericHandler, +) (srv *http.Server, lis net.Listener, err error) { + router := &Router{r: mux.NewRouter()} + + setupDebugHandlers(router) + setupGraphQLHandlers(router, wallet) + setupIPFSFileHandlers(router, ipfsHandler) + setupGRPCWebHandler(router, rpc) + for _, handler := range extraHandlers { + router.Handle(handler.Path, handler.Handler, handler.Mode) + } + router.Handle("/", http.HandlerFunc(router.Index), 0) + + srv = &http.Server{ + Addr: ":" + strconv.Itoa(port), + ReadHeaderTimeout: 5 * time.Second, + // WriteTimeout: 10 * time.Second, + IdleTimeout: 20 * time.Second, + Handler: router.r, + } + + lis, err = net.Listen("tcp", srv.Addr) + if err != nil { + return + } + + g.Go(func() error { + err := srv.Serve(lis) + if err == http.ErrServerClosed { + return nil + } + return err + }) + + clean.AddErrFunc(func() error { + return srv.Shutdown(context.Background()) + }) + + return +} + +// corsMiddleware allows different host/origins. +func corsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // allow cross domain AJAX requests + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") + next.ServeHTTP(w, r) + }) +} + +func buildInfoHandler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + info, ok := debug.ReadBuildInfo() + if !ok { + http.Error(w, "doesn't support build info", http.StatusExpectationFailed) + return + } + + // Don't want to show information about all the dependencies. + info.Deps = nil + + // Want to support text and json. + wantJSON := slices.Contains(r.Header.Values("Accept"), "application/json") || + r.URL.Query().Get("format") == "json" + + if wantJSON { + w.Header().Set("Content-Type", "application/json") + + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + + if err := enc.Encode(info); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } else { + w.Header().Set("Content-Type", "text/plain") + fmt.Fprint(w, info.String()) + } + }) +} diff --git a/backend/daemon/storage/migrations.go b/backend/daemon/storage/migrations.go index 2428aedb64..92bcb0f9e5 100644 --- a/backend/daemon/storage/migrations.go +++ b/backend/daemon/storage/migrations.go @@ -51,6 +51,47 @@ var migrations = []migration{ {Version: "2023-08-30.01", Run: func(d *Dir, conn *sqlite.Conn) error { return nil }}, + {Version: "2023-09-12.01", Run: func(d *Dir, conn *sqlite.Conn) error { + return sqlitex.ExecScript(conn, ` + DROP TABLE sites; + DROP TABLE invite_tokens; + DROP TABLE site_members; + DROP TABLE web_publications; + `) + }}, + {Version: "2023-09-18.01", Run: func(d *Dir, conn *sqlite.Conn) error { + return sqlitex.ExecScript(conn, ` + DROP TABLE served_sites; + `) + }}, + {Version: "2023-09-18.02", Run: func(d *Dir, conn *sqlite.Conn) error { + return sqlitex.ExecScript(conn, sqlfmt(` + CREATE TABLE remote_sites ( + url TEXT UNIQUE NOT NULL, + peer_id TEXT NOT NULL, + group_id TEXT NOT NULL, + group_version TEXT NOT NULL, + last_sync_time INTEGER NOT NULL, + last_ok_sync_time INTEGER NOT NULL + ); + `)) + }}, + {Version: "2023-09-19.01", Run: func(d *Dir, conn *sqlite.Conn) error { + return sqlitex.ExecScript(conn, sqlfmt(` + CREATE TABLE accounts ( + entity INTEGER REFERENCES lookup (id) ON DELETE CASCADE NOT NULL, + public_key INTEGER REFERENCES lookup (id) ON DELETE CASCADE NOT NULL, + PRIMARY KEY (entity, public_key) + ); + + CREATE INDEX accounts_by_key ON accounts (public_key, entity); + `)) + }}, + {Version: "2023-09-20.01", Run: func(d *Dir, conn *sqlite.Conn) error { + return sqlitex.ExecScript(conn, sqlfmt(` + DELETE FROM kv WHERE key = 'last_reindex_time'; + `)) + }}, } const ( @@ -91,7 +132,7 @@ func (d *Dir) init() (currentVersion string, err error) { } if d.device.Wrapped() == nil { - kp, err := core.NewKeyPairRandom(core.CodecDeviceKey) + kp, err := core.NewKeyPairRandom() if err != nil { return "", fmt.Errorf("failed to generate random device key: %w", err) } @@ -204,7 +245,7 @@ func (d *Dir) maybeLoadAccountKey() error { return err } - account, err := core.NewPublicKey(core.CodecAccountKey, pub) + account, err := core.NewPublicKey(pub) if err != nil { return err } @@ -275,5 +316,5 @@ func loadDeviceKeyFromFile(dir string) (kp core.KeyPair, err error) { return kp, fmt.Errorf("failed to unmarshal private key for device: %w", err) } - return core.NewKeyPair(core.CodecDeviceKey, pk) + return core.NewKeyPair(pk) } diff --git a/backend/daemon/storage/migrations_test.go b/backend/daemon/storage/migrations_test.go index 354950aba3..d04030e155 100644 --- a/backend/daemon/storage/migrations_test.go +++ b/backend/daemon/storage/migrations_test.go @@ -17,19 +17,6 @@ import ( ) func TestMigrateMatchesFreshSchema(t *testing.T) { - // We made a new breaking change so we have no migrations now. - // We can skip this test until we add at least one new migration, - // in which case we need to generate the initial data dir snapshot, - // and store it in testdata. Then we should apply migrations - // on top of this snapshot and verify that it has the same structure - // as new freshly created data dir. - - if len(migrations) == 1 { - t.SkipNow() - } - - t.Fatalf("We now have some migrations. Fix this text!") - // We have manually snapshot the data dir from before the migration framework was implemented. // It's stored in ./testdata/initial-data-dir. // We want to test that the data dir with all applied migrations matches the data dir created from scratch. @@ -37,7 +24,7 @@ func TestMigrateMatchesFreshSchema(t *testing.T) { // and then compare it with a fresh directory. tmpDir := t.TempDir() - err := copyDir("./testdata/initial-data-dir", tmpDir) + err := copyDir("./testdata/mintter-test-db-snapshot", tmpDir) require.NoError(t, err) oldDir, err := New(tmpDir, zap.NewNop()) diff --git a/backend/daemon/storage/schema.gen.go b/backend/daemon/storage/schema.gen.go index 763dcfab62..1dcf2cf6d4 100644 --- a/backend/daemon/storage/schema.gen.go +++ b/backend/daemon/storage/schema.gen.go @@ -6,6 +6,20 @@ import ( "mintter/backend/pkg/sqlitegen" ) +// Table accounts. +const ( + Accounts sqlitegen.Table = "accounts" + AccountsEntity sqlitegen.Column = "accounts.entity" + AccountsPublicKey sqlitegen.Column = "accounts.public_key" +) + +// Table accounts. Plain strings. +const ( + T_Accounts = "accounts" + C_AccountsEntity = "accounts.entity" + C_AccountsPublicKey = "accounts.public_key" +) + // Table blob_attrs. const ( BlobAttrs sqlitegen.Table = "blob_attrs" @@ -192,22 +206,6 @@ const ( C_HeadsResource = "heads.resource" ) -// Table invite_tokens. -const ( - InviteTokens sqlitegen.Table = "invite_tokens" - InviteTokensExpireTime sqlitegen.Column = "invite_tokens.expire_time" - InviteTokensRole sqlitegen.Column = "invite_tokens.role" - InviteTokensToken sqlitegen.Column = "invite_tokens.token" -) - -// Table invite_tokens. Plain strings. -const ( - T_InviteTokens = "invite_tokens" - C_InviteTokensExpireTime = "invite_tokens.expire_time" - C_InviteTokensRole = "invite_tokens.role" - C_InviteTokensToken = "invite_tokens.token" -) - // Table key_delegations. const ( KeyDelegations sqlitegen.Table = "key_delegations" @@ -304,54 +302,26 @@ const ( C_PublicKeysPrincipal = "public_keys.principal" ) -// Table served_sites. -const ( - ServedSites sqlitegen.Table = "served_sites" - ServedSitesGroupID sqlitegen.Column = "served_sites.group_id" - ServedSitesHostname sqlitegen.Column = "served_sites.hostname" - ServedSitesOwnerID sqlitegen.Column = "served_sites.owner_id" - ServedSitesVersion sqlitegen.Column = "served_sites.version" -) - -// Table served_sites. Plain strings. +// Table remote_sites. const ( - T_ServedSites = "served_sites" - C_ServedSitesGroupID = "served_sites.group_id" - C_ServedSitesHostname = "served_sites.hostname" - C_ServedSitesOwnerID = "served_sites.owner_id" - C_ServedSitesVersion = "served_sites.version" + RemoteSites sqlitegen.Table = "remote_sites" + RemoteSitesGroupID sqlitegen.Column = "remote_sites.group_id" + RemoteSitesGroupVersion sqlitegen.Column = "remote_sites.group_version" + RemoteSitesLastOkSyncTime sqlitegen.Column = "remote_sites.last_ok_sync_time" + RemoteSitesLastSyncTime sqlitegen.Column = "remote_sites.last_sync_time" + RemoteSitesPeerID sqlitegen.Column = "remote_sites.peer_id" + RemoteSitesURL sqlitegen.Column = "remote_sites.url" ) -// Table site_members. +// Table remote_sites. Plain strings. const ( - SiteMembers sqlitegen.Table = "site_members" - SiteMembersAccountID sqlitegen.Column = "site_members.account_id" - SiteMembersRole sqlitegen.Column = "site_members.role" -) - -// Table site_members. Plain strings. -const ( - T_SiteMembers = "site_members" - C_SiteMembersAccountID = "site_members.account_id" - C_SiteMembersRole = "site_members.role" -) - -// Table sites. -const ( - Sites sqlitegen.Table = "sites" - SitesAccountID sqlitegen.Column = "sites.account_id" - SitesAddresses sqlitegen.Column = "sites.addresses" - SitesHostname sqlitegen.Column = "sites.hostname" - SitesRole sqlitegen.Column = "sites.role" -) - -// Table sites. Plain strings. -const ( - T_Sites = "sites" - C_SitesAccountID = "sites.account_id" - C_SitesAddresses = "sites.addresses" - C_SitesHostname = "sites.hostname" - C_SitesRole = "sites.role" + T_RemoteSites = "remote_sites" + C_RemoteSitesGroupID = "remote_sites.group_id" + C_RemoteSitesGroupVersion = "remote_sites.group_version" + C_RemoteSitesLastOkSyncTime = "remote_sites.last_ok_sync_time" + C_RemoteSitesLastSyncTime = "remote_sites.last_sync_time" + C_RemoteSitesPeerID = "remote_sites.peer_id" + C_RemoteSitesURL = "remote_sites.url" ) // Table sqlite_sequence. @@ -406,25 +376,11 @@ const ( C_WalletsType = "wallets.type" ) -// Table web_publications. -const ( - WebPublications sqlitegen.Table = "web_publications" - WebPublicationsEID sqlitegen.Column = "web_publications.eid" - WebPublicationsPath sqlitegen.Column = "web_publications.path" - WebPublicationsVersion sqlitegen.Column = "web_publications.version" -) - -// Table web_publications. Plain strings. -const ( - T_WebPublications = "web_publications" - C_WebPublicationsEID = "web_publications.eid" - C_WebPublicationsPath = "web_publications.path" - C_WebPublicationsVersion = "web_publications.version" -) - // Schema describes SQLite columns. var Schema = sqlitegen.Schema{ Columns: map[sqlitegen.Column]sqlitegen.ColumnInfo{ + AccountsEntity: {Table: Accounts, SQLType: "INTEGER"}, + AccountsPublicKey: {Table: Accounts, SQLType: "INTEGER"}, BlobAttrsAnchor: {Table: BlobAttrs, SQLType: "TEXT"}, BlobAttrsBlob: {Table: BlobAttrs, SQLType: "INTEGER"}, BlobAttrsExtra: {Table: BlobAttrs, SQLType: ""}, @@ -468,9 +424,6 @@ var Schema = sqlitegen.Schema{ HeadsBlob: {Table: Heads, SQLType: "INTEGER"}, HeadsName: {Table: Heads, SQLType: "TEXT"}, HeadsResource: {Table: Heads, SQLType: "INTEGER"}, - InviteTokensExpireTime: {Table: InviteTokens, SQLType: "INTEGER"}, - InviteTokensRole: {Table: InviteTokens, SQLType: "INTEGER"}, - InviteTokensToken: {Table: InviteTokens, SQLType: "TEXT"}, KeyDelegationsBlob: {Table: KeyDelegations, SQLType: "INTEGER"}, KeyDelegationsDelegate: {Table: KeyDelegations, SQLType: ""}, KeyDelegationsIssuer: {Table: KeyDelegations, SQLType: ""}, @@ -489,16 +442,12 @@ var Schema = sqlitegen.Schema{ PublicBlobsViewMultihash: {Table: PublicBlobsView, SQLType: "BLOB"}, PublicKeysID: {Table: PublicKeys, SQLType: "INTEGER"}, PublicKeysPrincipal: {Table: PublicKeys, SQLType: "BLOB"}, - ServedSitesGroupID: {Table: ServedSites, SQLType: "INTEGER"}, - ServedSitesHostname: {Table: ServedSites, SQLType: "TEXT"}, - ServedSitesOwnerID: {Table: ServedSites, SQLType: "INTEGER"}, - ServedSitesVersion: {Table: ServedSites, SQLType: "TEXT"}, - SiteMembersAccountID: {Table: SiteMembers, SQLType: "INTEGER"}, - SiteMembersRole: {Table: SiteMembers, SQLType: "INTEGER"}, - SitesAccountID: {Table: Sites, SQLType: "INTEGER"}, - SitesAddresses: {Table: Sites, SQLType: "TEXT"}, - SitesHostname: {Table: Sites, SQLType: "TEXT"}, - SitesRole: {Table: Sites, SQLType: "INTEGER"}, + RemoteSitesGroupID: {Table: RemoteSites, SQLType: "TEXT"}, + RemoteSitesGroupVersion: {Table: RemoteSites, SQLType: "TEXT"}, + RemoteSitesLastOkSyncTime: {Table: RemoteSites, SQLType: "INTEGER"}, + RemoteSitesLastSyncTime: {Table: RemoteSites, SQLType: "INTEGER"}, + RemoteSitesPeerID: {Table: RemoteSites, SQLType: "TEXT"}, + RemoteSitesURL: {Table: RemoteSites, SQLType: "TEXT"}, SQLiteSequenceName: {Table: SQLiteSequence, SQLType: ""}, SQLiteSequenceSeq: {Table: SQLiteSequence, SQLType: ""}, TrustedAccountsID: {Table: TrustedAccounts, SQLType: "INTEGER"}, @@ -510,8 +459,5 @@ var Schema = sqlitegen.Schema{ WalletsPassword: {Table: Wallets, SQLType: "BLOB"}, WalletsToken: {Table: Wallets, SQLType: "BLOB"}, WalletsType: {Table: Wallets, SQLType: "TEXT"}, - WebPublicationsEID: {Table: WebPublications, SQLType: "TEXT"}, - WebPublicationsPath: {Table: WebPublications, SQLType: "TEXT"}, - WebPublicationsVersion: {Table: WebPublications, SQLType: "TEXT"}, }, } diff --git a/backend/daemon/storage/schema.gensum b/backend/daemon/storage/schema.gensum index 7aa43fe7de..89278961b4 100644 --- a/backend/daemon/storage/schema.gensum +++ b/backend/daemon/storage/schema.gensum @@ -1,2 +1,2 @@ -srcs: ac0e621b7e18d8a7bad3dd5f99dda1e6 -outs: 1668302d9dde81889419d6a3a94b2d8f +srcs: 1bf2bad01aa8878b5b11c49322b14f57 +outs: 4bcbab63b12a9ae9bc23f071a4a317ab diff --git a/backend/daemon/storage/schema.go b/backend/daemon/storage/schema.go index 374cdafa78..61350f9815 100644 --- a/backend/daemon/storage/schema.go +++ b/backend/daemon/storage/schema.go @@ -2,8 +2,6 @@ package storage import ( _ "embed" - "regexp" - "strings" ) // Types for the lookup table. @@ -40,35 +38,3 @@ func init() { col.SQLType = "BLOB" Schema.Columns[BlobAttrsExtra] = col } - -// removeSQLComments is written with the help of ChatGPT, but it seems to work. -// We don't need to store comments in the database file, but we want to use them for ourselves. -func removeSQLComments(sql string) string { - re := regexp.MustCompile(`('[^']*')|--.*|/\*[\s\S]*?\*/`) // Regular expression to match SQL comments and string literals - lines := strings.Split(sql, "\n") // Split SQL statement into lines - outLines := make([]string, 0, len(lines)) - for _, line := range lines { - line = re.ReplaceAllStringFunc(line, func(match string) string { - if strings.HasPrefix(match, "--") { - return "" // Remove single-line comments - } else if strings.HasPrefix(match, "/*") { - return "" // Remove multi-line comments - } else { - return match // Preserve string literals - } - }) - // Lines with only comments end up being empty, and we don't want those. - if strings.TrimSpace(line) == "" { - continue - } - // We don't want trailing new lines, because we'll be joining lines later. - line = strings.Trim(line, "\r\n") - // For more convenient formatting, all of our migration statement would have - // an extra tab at the beginning of the line, we can get rid of it. - if line[0] == '\t' { - line = line[1:] - } - outLines = append(outLines, line) - } - return strings.Join(outLines, "\n") // Join lines back together -} diff --git a/backend/daemon/storage/schema.sql b/backend/daemon/storage/schema.sql index b1956aecbb..12ca474948 100644 --- a/backend/daemon/storage/schema.sql +++ b/backend/daemon/storage/schema.sql @@ -225,71 +225,26 @@ CREATE TABLE wallets ( balance INTEGER DEFAULT 0 ); --- Stores sites that user has manually added -CREATE TABLE sites ( - -- Site unique identification. The hostname of the site with protocol https://example.com - hostname TEXT PRIMARY KEY CHECK(hostname <> ''), - -- The role we play in the site ROLE_UNSPECIFIED = 0 | OWNER = 1 | EDITOR = 2 - role INTEGER NOT NULL DEFAULT 0, - -- P2P addresses to connect to that site in the format of multiaddresses. Space separated. - addresses TEXT NOT NULL CHECK(addresses <> ''), - -- The account ID of the site. We need a previous connection to the site so the - -- actual account is inserted in the accounts table when handshake. - account_id INTEGER REFERENCES lookup (id) ON DELETE CASCADE NOT NULL -) WITHOUT ROWID; - -CREATE INDEX sites_by_account ON sites (account_id); - --- Table that stores all the tokens not yet redeemed inside a site. Although this table is relevant only --- for sites at the beginning, keep in mind that any regular node can be upgraded to a site. -CREATE TABLE invite_tokens ( - -- Unique token identification. Random string. - token TEXT PRIMARY KEY CHECK(token <> ''), - -- The member role for the user that will redeem the token. - -- OWNER = 1 | EDITOR = 2. - role INTEGER NOT NULL CHECK (role != 0), - -- Timestamp since the token will no longer be eligible to be redeemed. Seconds since Jan 1, 1970 - expire_time INTEGER NOT NULL CHECK (expire_time > 0) -) WITHOUT ROWID; - --- Table that stores the role each account has inside a site. Although this table is relevant only --- for sites at the beginning, keep in mind that any regular node can be upgraded to a site. -CREATE TABLE site_members ( - -- The account id that has been linked to a role on this site - account_id INTEGER REFERENCES lookup (id) ON DELETE CASCADE NOT NULL, - -- The role of the site member. - -- OWNER = 1 | EDITOR = 2. - role INTEGER NOT NULL CHECK (role != 0), - PRIMARY KEY (account_id) -) WITHOUT ROWID; - --- We currently only allow one owner per site. -CREATE UNIQUE INDEX idx_site_owner ON site_members (role) WHERE role = 1; - --- Stores all the records published on this site. Although this table is relevant only --- for sites at the beginning, keep in mind that any regular node can be upgraded to a site. -CREATE TABLE web_publications ( - -- Entity ID of the published document. - eid TEXT PRIMARY KEY CHECK (eid <> ''), - -- doc version of the base document published. Not its references. - version TEXT NOT NULL, - -- Path this publication is published to. If NULL is not listed. - path TEXT UNIQUE +-- Stores remote sites and their syncing status. +CREATE TABLE remote_sites ( + -- Values below are stable and are used to validate + -- whether site and group information correspond to each other. + url TEXT UNIQUE NOT NULL, + peer_id TEXT NOT NULL, + group_id TEXT NOT NULL, + -- Values below are updated on each sync and used for caching. + group_version TEXT NOT NULL, + last_sync_time INTEGER NOT NULL, + last_ok_sync_time INTEGER NOT NULL ); --- Stores all the sites served locally. Sites are a a group + domain. --- for sites at the beginning, keep in mind that any regular node can be upgraded to a site. -CREATE TABLE served_sites ( - -- the domain + protocol the site is served in. - hostname TEXT CHECK (hostname <> '') PRIMARY KEY, - -- entity ID of the group the site is associated with. - group_id INTEGER REFERENCES lookup (id) NOT NULL, - -- the version of the group the site is serving. - version TEXT NOT NULL, - -- account id of the owner of the group. - owner_id INTEGER REFERENCES lookup (id) NOT NULL, - -- same version + groupid cannot be published in different histnames. - UNIQUE(group_id, version) ON CONFLICT REPLACE +-- Stores mapping between account public keys +-- and their entity IDs. +CREATE TABLE accounts ( + entity INTEGER REFERENCES lookup (id) ON DELETE CASCADE NOT NULL, + public_key INTEGER REFERENCES lookup (id) ON DELETE CASCADE NOT NULL, + PRIMARY KEY (entity, public_key) ); -CREATE INDEX served_sites_by_owner ON served_sites (owner_id); +-- Index to query entity ID of an account public key. +CREATE INDEX accounts_by_key ON accounts (public_key, entity); diff --git a/backend/daemon/storage/sqlfmt.go b/backend/daemon/storage/sqlfmt.go index 21d43b5c95..841f9258da 100644 --- a/backend/daemon/storage/sqlfmt.go +++ b/backend/daemon/storage/sqlfmt.go @@ -51,3 +51,35 @@ func sqlfmt(text string) string { strings.Replace(text, "\t", " ", -1), ) } + +// removeSQLComments is written with the help of ChatGPT, but it seems to work. +// We don't need to store comments in the database file, but we want to use them for ourselves. +func removeSQLComments(sql string) string { + re := regexp.MustCompile(`('[^']*')|--.*|/\*[\s\S]*?\*/`) // Regular expression to match SQL comments and string literals + lines := strings.Split(sql, "\n") // Split SQL statement into lines + outLines := make([]string, 0, len(lines)) + for _, line := range lines { + line = re.ReplaceAllStringFunc(line, func(match string) string { + if strings.HasPrefix(match, "--") { + return "" // Remove single-line comments + } else if strings.HasPrefix(match, "/*") { + return "" // Remove multi-line comments + } else { + return match // Preserve string literals + } + }) + // Lines with only comments end up being empty, and we don't want those. + if strings.TrimSpace(line) == "" { + continue + } + // We don't want trailing new lines, because we'll be joining lines later. + line = strings.Trim(line, "\r\n") + // For more convenient formatting, all of our migration statement would have + // an extra tab at the beginning of the line, we can get rid of it. + if line[0] == '\t' { + line = line[1:] + } + outLines = append(outLines, line) + } + return strings.Join(outLines, "\n") // Join lines back together +} diff --git a/backend/daemon/storage/storage.go b/backend/daemon/storage/storage.go index e8f7547342..15777e1976 100644 --- a/backend/daemon/storage/storage.go +++ b/backend/daemon/storage/storage.go @@ -41,7 +41,7 @@ func NewWithDeviceKey(path string, log *zap.Logger, pk crypto.PrivKey) (*Dir, er return nil, err } - kp, err := core.NewKeyPair(core.CodecDeviceKey, pk) + kp, err := core.NewKeyPair(pk) if err != nil { return nil, err } diff --git a/backend/daemon/storage/testdata/initial-data-dir/VERSION b/backend/daemon/storage/testdata/initial-data-dir/VERSION deleted file mode 100644 index a770f5aa44..0000000000 --- a/backend/daemon/storage/testdata/initial-data-dir/VERSION +++ /dev/null @@ -1 +0,0 @@ -2023-06-26.01 \ No newline at end of file diff --git a/backend/daemon/storage/testdata/initial-data-dir/keys/libp2p_id_ed25519 b/backend/daemon/storage/testdata/initial-data-dir/keys/libp2p_id_ed25519 deleted file mode 100644 index c782a9cb23..0000000000 --- a/backend/daemon/storage/testdata/initial-data-dir/keys/libp2p_id_ed25519 +++ /dev/null @@ -1 +0,0 @@ -@úE™6ù¦"ÂçZùÏœÕþŸX¡¡>Mò×R yèdæ7Ä^㌛C3õøÉÕp%ª)¸ÑRÖªý_‹…ÓŸ \ No newline at end of file diff --git a/backend/daemon/storage/testdata/initial-data-dir/keys/mintter_id_ed25519.pub b/backend/daemon/storage/testdata/initial-data-dir/keys/mintter_id_ed25519.pub deleted file mode 100644 index f3feeb501e..0000000000 --- a/backend/daemon/storage/testdata/initial-data-dir/keys/mintter_id_ed25519.pub +++ /dev/null @@ -1 +0,0 @@ - _{£¢›llt‘ÒÆD}N󶦜³ÂcšæåXd½LAB5 \ No newline at end of file diff --git a/backend/daemon/storage/testdata/initial-data-dir/mintterd.conf b/backend/daemon/storage/testdata/initial-data-dir/mintterd.conf deleted file mode 100644 index d3026ea215..0000000000 --- a/backend/daemon/storage/testdata/initial-data-dir/mintterd.conf +++ /dev/null @@ -1,2 +0,0 @@ -# Config file for the mintterd program. -# You can set any CLI flags here, one per line with a space between key and value. diff --git a/backend/daemon/storage/testdata/mintter-test-db-snapshot/VERSION b/backend/daemon/storage/testdata/mintter-test-db-snapshot/VERSION new file mode 100644 index 0000000000..cdf931172e --- /dev/null +++ b/backend/daemon/storage/testdata/mintter-test-db-snapshot/VERSION @@ -0,0 +1 @@ +2023-08-30.01 \ No newline at end of file diff --git a/backend/daemon/storage/testdata/initial-data-dir/db/db.sqlite b/backend/daemon/storage/testdata/mintter-test-db-snapshot/db/db.sqlite similarity index 98% rename from backend/daemon/storage/testdata/initial-data-dir/db/db.sqlite rename to backend/daemon/storage/testdata/mintter-test-db-snapshot/db/db.sqlite index 1060506bb8..eb43b2d6fc 100644 Binary files a/backend/daemon/storage/testdata/initial-data-dir/db/db.sqlite and b/backend/daemon/storage/testdata/mintter-test-db-snapshot/db/db.sqlite differ diff --git a/backend/daemon/storage/testdata/initial-data-dir/db/db.sqlite-shm b/backend/daemon/storage/testdata/mintter-test-db-snapshot/db/db.sqlite-shm similarity index 92% rename from backend/daemon/storage/testdata/initial-data-dir/db/db.sqlite-shm rename to backend/daemon/storage/testdata/mintter-test-db-snapshot/db/db.sqlite-shm index 9c837c6235..aed9e57939 100644 Binary files a/backend/daemon/storage/testdata/initial-data-dir/db/db.sqlite-shm and b/backend/daemon/storage/testdata/mintter-test-db-snapshot/db/db.sqlite-shm differ diff --git a/backend/daemon/storage/testdata/initial-data-dir/db/db.sqlite-wal b/backend/daemon/storage/testdata/mintter-test-db-snapshot/db/db.sqlite-wal similarity index 80% rename from backend/daemon/storage/testdata/initial-data-dir/db/db.sqlite-wal rename to backend/daemon/storage/testdata/mintter-test-db-snapshot/db/db.sqlite-wal index ea1c05fef9..22e3f890f0 100644 Binary files a/backend/daemon/storage/testdata/initial-data-dir/db/db.sqlite-wal and b/backend/daemon/storage/testdata/mintter-test-db-snapshot/db/db.sqlite-wal differ diff --git a/backend/daemon/storage/testdata/mintter-test-db-snapshot/keys/libp2p_id_ed25519 b/backend/daemon/storage/testdata/mintter-test-db-snapshot/keys/libp2p_id_ed25519 new file mode 100644 index 0000000000..b8d6c51662 --- /dev/null +++ b/backend/daemon/storage/testdata/mintter-test-db-snapshot/keys/libp2p_id_ed25519 @@ -0,0 +1 @@ +@Õèëû¦ZÄ(\uÝ-w@^°úö¥¦8HHchT»1RA>ý;^×àBª›g8©÷&ÕãÐñiºŒ•+Ú‘E2‡ \ No newline at end of file diff --git a/backend/daemon/storage/testdata/mintter-test-db-snapshot/keys/mintter_id_ed25519.pub b/backend/daemon/storage/testdata/mintter-test-db-snapshot/keys/mintter_id_ed25519.pub new file mode 100644 index 0000000000..b9f9a3a9ec --- /dev/null +++ b/backend/daemon/storage/testdata/mintter-test-db-snapshot/keys/mintter_id_ed25519.pub @@ -0,0 +1 @@ + êÏZ¾õ  ê‹î&šR6ï¹› 3A¬0¥Ç"þ` \ No newline at end of file diff --git a/backend/genproto/accounts/v1alpha/accounts.pb.go b/backend/genproto/accounts/v1alpha/accounts.pb.go index 122012ca02..4060fb0973 100644 --- a/backend/genproto/accounts/v1alpha/accounts.pb.go +++ b/backend/genproto/accounts/v1alpha/accounts.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.21.12 // source: accounts/v1alpha/accounts.proto diff --git a/backend/genproto/daemon/v1alpha/daemon.pb.go b/backend/genproto/daemon/v1alpha/daemon.pb.go index 7558dd508c..9dcf3154f3 100644 --- a/backend/genproto/daemon/v1alpha/daemon.pb.go +++ b/backend/genproto/daemon/v1alpha/daemon.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.21.12 // source: daemon/v1alpha/daemon.proto diff --git a/backend/genproto/documents/v1alpha/changes.pb.go b/backend/genproto/documents/v1alpha/changes.pb.go index f8bfa3065c..06d7432849 100644 --- a/backend/genproto/documents/v1alpha/changes.pb.go +++ b/backend/genproto/documents/v1alpha/changes.pb.go @@ -2,7 +2,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.21.12 // source: documents/v1alpha/changes.proto diff --git a/backend/genproto/documents/v1alpha/comments.pb.go b/backend/genproto/documents/v1alpha/comments.pb.go index 8074771030..3fc841379b 100644 --- a/backend/genproto/documents/v1alpha/comments.pb.go +++ b/backend/genproto/documents/v1alpha/comments.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.21.12 // source: documents/v1alpha/comments.proto diff --git a/backend/genproto/documents/v1alpha/content_graph.pb.go b/backend/genproto/documents/v1alpha/content_graph.pb.go index b764c3fbb5..755fd8698a 100644 --- a/backend/genproto/documents/v1alpha/content_graph.pb.go +++ b/backend/genproto/documents/v1alpha/content_graph.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.21.12 // source: documents/v1alpha/content_graph.proto diff --git a/backend/genproto/documents/v1alpha/documents.pb.go b/backend/genproto/documents/v1alpha/documents.pb.go index f41ee5c0c8..53c03b47b1 100644 --- a/backend/genproto/documents/v1alpha/documents.pb.go +++ b/backend/genproto/documents/v1alpha/documents.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.21.12 // source: documents/v1alpha/documents.proto @@ -300,7 +300,6 @@ type DocumentChange struct { // *DocumentChange_MoveBlock_ // *DocumentChange_ReplaceBlock // *DocumentChange_DeleteBlock - // *DocumentChange_SetWebUrl Op isDocumentChange_Op `protobuf_oneof:"op"` } @@ -371,13 +370,6 @@ func (x *DocumentChange) GetDeleteBlock() string { return "" } -func (x *DocumentChange) GetSetWebUrl() string { - if x, ok := x.GetOp().(*DocumentChange_SetWebUrl); ok { - return x.SetWebUrl - } - return "" -} - type isDocumentChange_Op interface { isDocumentChange_Op() } @@ -402,11 +394,6 @@ type DocumentChange_DeleteBlock struct { DeleteBlock string `protobuf:"bytes,5,opt,name=delete_block,json=deleteBlock,proto3,oneof"` } -type DocumentChange_SetWebUrl struct { - // Sets Web URL where site is published. - SetWebUrl string `protobuf:"bytes,6,opt,name=set_web_url,json=setWebUrl,proto3,oneof"` -} - func (*DocumentChange_SetTitle) isDocumentChange_Op() {} func (*DocumentChange_MoveBlock_) isDocumentChange_Op() {} @@ -415,8 +402,6 @@ func (*DocumentChange_ReplaceBlock) isDocumentChange_Op() {} func (*DocumentChange_DeleteBlock) isDocumentChange_Op() {} -func (*DocumentChange_SetWebUrl) isDocumentChange_Op() {} - // Request to list stored drafts. type ListDraftsRequest struct { state protoimpl.MessageState @@ -918,8 +903,6 @@ type Document struct { Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"` // Output only. Author ID of the document. Author string `protobuf:"bytes,4,opt,name=author,proto3" json:"author,omitempty"` - // Web URL where this document is claimed to be published at. - WebUrl string `protobuf:"bytes,10,opt,name=web_url,json=webUrl,proto3" json:"web_url,omitempty"` // Output only. Account IDs of all the editors of the document. // Includes the original author as well. Editors []string `protobuf:"bytes,11,rep,name=editors,proto3" json:"editors,omitempty"` @@ -986,13 +969,6 @@ func (x *Document) GetAuthor() string { return "" } -func (x *Document) GetWebUrl() string { - if x != nil { - return x.WebUrl - } - return "" -} - func (x *Document) GetEditors() []string { if x != nil { return x.Editors @@ -1390,7 +1366,7 @@ var file_documents_v1alpha_documents_proto_rawDesc = []byte{ 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x49, - 0x64, 0x22, 0x86, 0x03, 0x0a, 0x0e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x68, + 0x64, 0x22, 0xe4, 0x02, 0x0a, 0x0e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x09, 0x73, 0x65, 0x74, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x73, 0x65, 0x74, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x58, 0x0a, 0x0a, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, @@ -1405,208 +1381,204 @@ var file_documents_v1alpha_documents_proto_rawDesc = []byte{ 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x00, 0x52, 0x0c, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x23, 0x0a, 0x0c, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x48, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, - 0x20, 0x0a, 0x0b, 0x73, 0x65, 0x74, 0x5f, 0x77, 0x65, 0x62, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x73, 0x65, 0x74, 0x57, 0x65, 0x62, 0x55, 0x72, - 0x6c, 0x1a, 0x61, 0x0a, 0x09, 0x4d, 0x6f, 0x76, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x19, - 0x0a, 0x08, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x61, 0x72, - 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x72, 0x65, 0x6e, - 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6c, 0x65, 0x66, 0x74, 0x5f, 0x73, 0x69, 0x62, 0x6c, 0x69, 0x6e, - 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6c, 0x65, 0x66, 0x74, 0x53, 0x69, 0x62, - 0x6c, 0x69, 0x6e, 0x67, 0x42, 0x04, 0x0a, 0x02, 0x6f, 0x70, 0x22, 0x4f, 0x0a, 0x11, 0x4c, 0x69, - 0x73, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x48, 0x00, 0x52, 0x0b, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x1a, + 0x61, 0x0a, 0x09, 0x4d, 0x6f, 0x76, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x19, 0x0a, 0x08, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x61, 0x72, 0x65, 0x6e, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x12, + 0x21, 0x0a, 0x0c, 0x6c, 0x65, 0x66, 0x74, 0x5f, 0x73, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6c, 0x65, 0x66, 0x74, 0x53, 0x69, 0x62, 0x6c, 0x69, + 0x6e, 0x67, 0x42, 0x04, 0x0a, 0x02, 0x6f, 0x70, 0x22, 0x4f, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, + 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, + 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x83, 0x01, 0x0a, 0x12, 0x4c, 0x69, + 0x73, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x45, 0x0a, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, + 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x09, 0x64, 0x6f, + 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x83, 0x01, 0x0a, 0x12, - 0x4c, 0x69, 0x73, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x45, 0x0a, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, + 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, + 0x36, 0x0a, 0x13, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x6f, 0x63, + 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x94, 0x01, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x74, + 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0b, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x3b, + 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, + 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x78, 0x0a, 0x17, 0x4c, + 0x69, 0x73, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, + 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, + 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x6e, + 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, + 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x92, 0x01, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, + 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, + 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x6c, 0x0a, 0x0b, 0x50, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x43, 0x0a, 0x08, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x09, - 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, - 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x22, 0x36, 0x0a, 0x13, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x72, 0x61, 0x66, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, 0x63, 0x75, - 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, - 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x94, 0x01, 0x0a, 0x15, 0x47, 0x65, - 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, - 0x6e, 0x74, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, - 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x21, 0x0a, - 0x0c, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0b, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x4f, 0x6e, 0x6c, 0x79, - 0x22, 0x3b, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, - 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x78, 0x0a, - 0x17, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, - 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, - 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, - 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x74, 0x72, 0x75, 0x73, - 0x74, 0x65, 0x64, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x92, 0x01, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, - 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6d, - 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, - 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x6c, 0x0a, 0x0b, - 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x43, 0x0a, 0x08, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, - 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, - 0x52, 0x08, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0xfa, 0x02, 0x0a, 0x08, 0x44, - 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x12, 0x17, 0x0a, 0x07, 0x77, 0x65, 0x62, 0x5f, 0x75, 0x72, 0x6c, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x77, 0x65, 0x62, 0x55, 0x72, 0x6c, 0x12, 0x18, - 0x0a, 0x07, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x07, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x44, 0x0a, 0x08, 0x63, 0x68, 0x69, 0x6c, - 0x64, 0x72, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x6f, 0x6d, - 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, - 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x12, 0x3b, - 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, + 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0xe1, 0x02, 0x0a, 0x08, 0x44, 0x6f, 0x63, + 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x0b, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x65, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x44, 0x0a, + 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x28, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, + 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, + 0x72, 0x65, 0x6e, 0x12, 0x3b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, + 0x12, 0x3b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3d, 0x0a, + 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3b, 0x0a, 0x0b, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x0c, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x73, 0x68, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x73, 0x68, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x8d, 0x01, 0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x3a, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, + 0x0b, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x8d, 0x01, 0x0a, + 0x09, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x3a, 0x0a, 0x05, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, + 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, + 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x44, 0x0a, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, + 0x65, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, + 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x6f, + 0x64, 0x65, 0x52, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x22, 0xcf, 0x02, 0x0a, + 0x05, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, + 0x78, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x12, 0x10, + 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, + 0x12, 0x54, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x12, 0x44, 0x0a, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, - 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x08, 0x63, - 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x22, 0xcf, 0x02, 0x0a, 0x05, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x54, 0x0a, 0x0a, 0x61, - 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x34, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, - 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, - 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, - 0x73, 0x12, 0x4b, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, - 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, - 0x0a, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, + 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x4b, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x6f, + 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, + 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xf8, + 0x01, 0x0a, 0x0a, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x72, 0x65, 0x66, 0x12, 0x59, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, + 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x05, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x05, 0x52, 0x04, 0x65, 0x6e, 0x64, 0x73, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xf8, 0x01, 0x0a, 0x0a, 0x41, 0x6e, - 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x03, - 0x72, 0x65, 0x66, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x59, - 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, - 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x74, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x61, - 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x73, 0x12, 0x12, 0x0a, 0x04, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x05, 0x52, - 0x04, 0x65, 0x6e, 0x64, 0x73, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x32, 0x8b, 0x05, 0x0a, 0x06, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x12, - 0x69, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x12, 0x31, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0x8b, 0x05, 0x0a, 0x06, 0x44, 0x72, + 0x61, 0x66, 0x74, 0x73, 0x12, 0x69, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x72, + 0x61, 0x66, 0x74, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, + 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, + 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, + 0x58, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, - 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, - 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x58, 0x0a, 0x0b, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, - 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x12, 0x63, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, - 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, - 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x2e, 0x47, 0x65, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, - 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x74, 0x0a, 0x0b, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, - 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, - 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x63, 0x6f, - 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x71, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x12, 0x30, 0x2e, + 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x63, 0x0a, 0x08, 0x47, 0x65, 0x74, + 0x44, 0x72, 0x61, 0x66, 0x74, 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, + 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, + 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x74, + 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, - 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x0c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x72, 0x61, - 0x66, 0x74, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x32, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, + 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x72, 0x61, 0x66, + 0x74, 0x73, 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x72, 0x61, 0x66, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, - 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x32, 0xee, 0x02, 0x0a, 0x0c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x72, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, - 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x63, 0x6f, + 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, + 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x72, 0x61, 0x66, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x0c, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x73, 0x68, 0x44, 0x72, 0x61, 0x66, 0x74, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, + 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, + 0x72, 0x61, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x64, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x2e, 0x63, - 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x83, 0x01, - 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x36, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, - 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x63, 0x6f, 0x6d, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x32, 0xee, 0x02, 0x0a, 0x0c, 0x50, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x72, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x42, 0x36, 0x5a, 0x34, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2f, 0x62, - 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x67, 0x65, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, - 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x3b, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, + 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x64, 0x0a, 0x11, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x37, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, + 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x12, 0x83, 0x01, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x36, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, + 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x37, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, + 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x36, 0x5a, 0x34, 0x6d, 0x69, 0x6e, 0x74, + 0x74, 0x65, 0x72, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x67, 0x65, 0x6e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x3b, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1928,7 +1900,6 @@ func file_documents_v1alpha_documents_proto_init() { (*DocumentChange_MoveBlock_)(nil), (*DocumentChange_ReplaceBlock)(nil), (*DocumentChange_DeleteBlock)(nil), - (*DocumentChange_SetWebUrl)(nil), } type x struct{} out := protoimpl.TypeBuilder{ diff --git a/backend/genproto/documents/v1alpha/web_publishing.pb.go b/backend/genproto/documents/v1alpha/web_publishing.pb.go deleted file mode 100644 index e167b40342..0000000000 --- a/backend/genproto/documents/v1alpha/web_publishing.pb.go +++ /dev/null @@ -1,2559 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.30.0 -// protoc v3.21.12 -// source: documents/v1alpha/web_publishing.proto - -package documents - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - emptypb "google.golang.org/protobuf/types/known/emptypb" - timestamppb "google.golang.org/protobuf/types/known/timestamppb" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// Roles a member of the Mintter Site can have. -type Member_Role int32 - -const ( - // Invalid default value. - Member_ROLE_UNSPECIFIED Member_Role = 0 - // Currently there can only be one owner of the site. - Member_OWNER Member_Role = 1 - // Editors are allowed to push content to the site. - Member_EDITOR Member_Role = 2 -) - -// Enum value maps for Member_Role. -var ( - Member_Role_name = map[int32]string{ - 0: "ROLE_UNSPECIFIED", - 1: "OWNER", - 2: "EDITOR", - } - Member_Role_value = map[string]int32{ - "ROLE_UNSPECIFIED": 0, - "OWNER": 1, - "EDITOR": 2, - } -) - -func (x Member_Role) Enum() *Member_Role { - p := new(Member_Role) - *p = x - return p -} - -func (x Member_Role) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (Member_Role) Descriptor() protoreflect.EnumDescriptor { - return file_documents_v1alpha_web_publishing_proto_enumTypes[0].Descriptor() -} - -func (Member_Role) Type() protoreflect.EnumType { - return &file_documents_v1alpha_web_publishing_proto_enumTypes[0] -} - -func (x Member_Role) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use Member_Role.Descriptor instead. -func (Member_Role) EnumDescriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{7, 0} -} - -// Request to add a site. -type AddSiteRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Required. Site hostname. - Hostname string `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"` - // Optional. Invite token for the site. Not needed - // if the site already knows our Account ID. - InviteToken string `protobuf:"bytes,2,opt,name=invite_token,json=inviteToken,proto3" json:"invite_token,omitempty"` -} - -func (x *AddSiteRequest) Reset() { - *x = AddSiteRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AddSiteRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AddSiteRequest) ProtoMessage() {} - -func (x *AddSiteRequest) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AddSiteRequest.ProtoReflect.Descriptor instead. -func (*AddSiteRequest) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{0} -} - -func (x *AddSiteRequest) GetHostname() string { - if x != nil { - return x.Hostname - } - return "" -} - -func (x *AddSiteRequest) GetInviteToken() string { - if x != nil { - return x.InviteToken - } - return "" -} - -// Request to remove a site from the local app backend. -type RemoveSiteRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Required. Site hostname. - Hostname string `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"` -} - -func (x *RemoveSiteRequest) Reset() { - *x = RemoveSiteRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RemoveSiteRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RemoveSiteRequest) ProtoMessage() {} - -func (x *RemoveSiteRequest) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RemoveSiteRequest.ProtoReflect.Descriptor instead. -func (*RemoveSiteRequest) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{1} -} - -func (x *RemoveSiteRequest) GetHostname() string { - if x != nil { - return x.Hostname - } - return "" -} - -// Request to list configured sites. -type ListSitesRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Optional. Number of items per page. - PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - // Optional. Token for a specific page. - PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` -} - -func (x *ListSitesRequest) Reset() { - *x = ListSitesRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ListSitesRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListSitesRequest) ProtoMessage() {} - -func (x *ListSitesRequest) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListSitesRequest.ProtoReflect.Descriptor instead. -func (*ListSitesRequest) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{2} -} - -func (x *ListSitesRequest) GetPageSize() int32 { - if x != nil { - return x.PageSize - } - return 0 -} - -func (x *ListSitesRequest) GetPageToken() string { - if x != nil { - return x.PageToken - } - return "" -} - -// Response with a list of sites. -type ListSitesResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // List of sites. - Sites []*SiteConfig `protobuf:"bytes,1,rep,name=sites,proto3" json:"sites,omitempty"` - // Token for the next page if any. - NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` -} - -func (x *ListSitesResponse) Reset() { - *x = ListSitesResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ListSitesResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListSitesResponse) ProtoMessage() {} - -func (x *ListSitesResponse) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListSitesResponse.ProtoReflect.Descriptor instead. -func (*ListSitesResponse) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{3} -} - -func (x *ListSitesResponse) GetSites() []*SiteConfig { - if x != nil { - return x.Sites - } - return nil -} - -func (x *ListSitesResponse) GetNextPageToken() string { - if x != nil { - return x.NextPageToken - } - return "" -} - -// Request to a list publication records for a given document -// among the configured web sites. -type ListWebPublicationRecordsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Required. Document ID. - DocumentId string `protobuf:"bytes,1,opt,name=document_id,json=documentId,proto3" json:"document_id,omitempty"` - // Optional. Specific version of a document. - Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` -} - -func (x *ListWebPublicationRecordsRequest) Reset() { - *x = ListWebPublicationRecordsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ListWebPublicationRecordsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListWebPublicationRecordsRequest) ProtoMessage() {} - -func (x *ListWebPublicationRecordsRequest) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListWebPublicationRecordsRequest.ProtoReflect.Descriptor instead. -func (*ListWebPublicationRecordsRequest) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{4} -} - -func (x *ListWebPublicationRecordsRequest) GetDocumentId() string { - if x != nil { - return x.DocumentId - } - return "" -} - -func (x *ListWebPublicationRecordsRequest) GetVersion() string { - if x != nil { - return x.Version - } - return "" -} - -// Response with a list of publication records among the web sites. -type ListWebPublicationRecordsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Publications []*WebPublicationRecord `protobuf:"bytes,1,rep,name=publications,proto3" json:"publications,omitempty"` -} - -func (x *ListWebPublicationRecordsResponse) Reset() { - *x = ListWebPublicationRecordsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ListWebPublicationRecordsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListWebPublicationRecordsResponse) ProtoMessage() {} - -func (x *ListWebPublicationRecordsResponse) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListWebPublicationRecordsResponse.ProtoReflect.Descriptor instead. -func (*ListWebPublicationRecordsResponse) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{5} -} - -func (x *ListWebPublicationRecordsResponse) GetPublications() []*WebPublicationRecord { - if x != nil { - return x.Publications - } - return nil -} - -// Local site configuration. -type SiteConfig struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Hostname of the site. - Hostname string `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"` - // Our role on this site. - Role Member_Role `protobuf:"varint,2,opt,name=role,proto3,enum=com.mintter.documents.v1alpha.Member_Role" json:"role,omitempty"` -} - -func (x *SiteConfig) Reset() { - *x = SiteConfig{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SiteConfig) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SiteConfig) ProtoMessage() {} - -func (x *SiteConfig) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SiteConfig.ProtoReflect.Descriptor instead. -func (*SiteConfig) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{6} -} - -func (x *SiteConfig) GetHostname() string { - if x != nil { - return x.Hostname - } - return "" -} - -func (x *SiteConfig) GetRole() Member_Role { - if x != nil { - return x.Role - } - return Member_ROLE_UNSPECIFIED -} - -// Member of the web site. -type Member struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Mintter Account ID of the member. - AccountId string `protobuf:"bytes,1,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` - // Member's role on the site. - Role Member_Role `protobuf:"varint,2,opt,name=role,proto3,enum=com.mintter.documents.v1alpha.Member_Role" json:"role,omitempty"` -} - -func (x *Member) Reset() { - *x = Member{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Member) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Member) ProtoMessage() {} - -func (x *Member) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Member.ProtoReflect.Descriptor instead. -func (*Member) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{7} -} - -func (x *Member) GetAccountId() string { - if x != nil { - return x.AccountId - } - return "" -} - -func (x *Member) GetRole() Member_Role { - if x != nil { - return x.Role - } - return Member_ROLE_UNSPECIFIED -} - -// Information about a Mintter Document published on a Mintter Site. -// One Document ID can be published with different versions and/or -// under different paths on a Mintter Web Site, so it can have -// multiple of these records per site. -type WebPublicationRecord struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - DocumentId string `protobuf:"bytes,1,opt,name=document_id,json=documentId,proto3" json:"document_id,omitempty"` - Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` - Hostname string `protobuf:"bytes,3,opt,name=hostname,proto3" json:"hostname,omitempty"` - Path string `protobuf:"bytes,4,opt,name=path,proto3" json:"path,omitempty"` -} - -func (x *WebPublicationRecord) Reset() { - *x = WebPublicationRecord{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WebPublicationRecord) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WebPublicationRecord) ProtoMessage() {} - -func (x *WebPublicationRecord) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WebPublicationRecord.ProtoReflect.Descriptor instead. -func (*WebPublicationRecord) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{8} -} - -func (x *WebPublicationRecord) GetDocumentId() string { - if x != nil { - return x.DocumentId - } - return "" -} - -func (x *WebPublicationRecord) GetVersion() string { - if x != nil { - return x.Version - } - return "" -} - -func (x *WebPublicationRecord) GetHostname() string { - if x != nil { - return x.Hostname - } - return "" -} - -func (x *WebPublicationRecord) GetPath() string { - if x != nil { - return x.Path - } - return "" -} - -// Request to create a new invite token. -type CreateInviteTokenRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Required. The role to be associated with the new member after invite is redeemed. - // The server might be configured to limit invites for certain roles, but it's not - // restricted in this API definition. - Role Member_Role `protobuf:"varint,1,opt,name=role,proto3,enum=com.mintter.documents.v1alpha.Member_Role" json:"role,omitempty"` - // Optional. The timestamp after which the invite token will expire if not redeemed. - // If not provided, the server will decide the deadline based on the internal configuration. - ExpireTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expire_time,json=expireTime,proto3" json:"expire_time,omitempty"` -} - -func (x *CreateInviteTokenRequest) Reset() { - *x = CreateInviteTokenRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CreateInviteTokenRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CreateInviteTokenRequest) ProtoMessage() {} - -func (x *CreateInviteTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CreateInviteTokenRequest.ProtoReflect.Descriptor instead. -func (*CreateInviteTokenRequest) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{9} -} - -func (x *CreateInviteTokenRequest) GetRole() Member_Role { - if x != nil { - return x.Role - } - return Member_ROLE_UNSPECIFIED -} - -func (x *CreateInviteTokenRequest) GetExpireTime() *timestamppb.Timestamp { - if x != nil { - return x.ExpireTime - } - return nil -} - -// Request to redeem an invite token. This is the most security-sensitive request, -// because it allows to register new members on the site. The server must obtain -// and verify the relation of the communicating peer with a corresponding Mintter Account. -// After the token is redeemed, a new member on the site must be created, associating -// the Mintter Account of the caller with the role invite token was created for. -type RedeemInviteTokenRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Value of the invite token. - Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` -} - -func (x *RedeemInviteTokenRequest) Reset() { - *x = RedeemInviteTokenRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RedeemInviteTokenRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RedeemInviteTokenRequest) ProtoMessage() {} - -func (x *RedeemInviteTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RedeemInviteTokenRequest.ProtoReflect.Descriptor instead. -func (*RedeemInviteTokenRequest) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{10} -} - -func (x *RedeemInviteTokenRequest) GetToken() string { - if x != nil { - return x.Token - } - return "" -} - -// Response after the token is redeemed. -type RedeemInviteTokenResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The role associated with the redeemed token - Role Member_Role `protobuf:"varint,1,opt,name=role,proto3,enum=com.mintter.documents.v1alpha.Member_Role" json:"role,omitempty"` -} - -func (x *RedeemInviteTokenResponse) Reset() { - *x = RedeemInviteTokenResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RedeemInviteTokenResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RedeemInviteTokenResponse) ProtoMessage() {} - -func (x *RedeemInviteTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RedeemInviteTokenResponse.ProtoReflect.Descriptor instead. -func (*RedeemInviteTokenResponse) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{11} -} - -func (x *RedeemInviteTokenResponse) GetRole() Member_Role { - if x != nil { - return x.Role - } - return Member_ROLE_UNSPECIFIED -} - -// Request to get the site info. -type GetSiteInfoRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *GetSiteInfoRequest) Reset() { - *x = GetSiteInfoRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetSiteInfoRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetSiteInfoRequest) ProtoMessage() {} - -func (x *GetSiteInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetSiteInfoRequest.ProtoReflect.Descriptor instead. -func (*GetSiteInfoRequest) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{12} -} - -// Request to update site info. Doesn't support partial updates. -type UpdateSiteInfoRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Title of the site. - Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` - // Description of the site. - Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` -} - -func (x *UpdateSiteInfoRequest) Reset() { - *x = UpdateSiteInfoRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *UpdateSiteInfoRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UpdateSiteInfoRequest) ProtoMessage() {} - -func (x *UpdateSiteInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UpdateSiteInfoRequest.ProtoReflect.Descriptor instead. -func (*UpdateSiteInfoRequest) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{13} -} - -func (x *UpdateSiteInfoRequest) GetTitle() string { - if x != nil { - return x.Title - } - return "" -} - -func (x *UpdateSiteInfoRequest) GetDescription() string { - if x != nil { - return x.Description - } - return "" -} - -// Request to list site members. -type ListMembersRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Optional. Number of items per page. - PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - // Optional. Token for a specific page. - PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` -} - -func (x *ListMembersRequest) Reset() { - *x = ListMembersRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ListMembersRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListMembersRequest) ProtoMessage() {} - -func (x *ListMembersRequest) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListMembersRequest.ProtoReflect.Descriptor instead. -func (*ListMembersRequest) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{14} -} - -func (x *ListMembersRequest) GetPageSize() int32 { - if x != nil { - return x.PageSize - } - return 0 -} - -func (x *ListMembersRequest) GetPageToken() string { - if x != nil { - return x.PageToken - } - return "" -} - -// Response listing site members. -type ListMembersResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // List of members. - Members []*Member `protobuf:"bytes,1,rep,name=members,proto3" json:"members,omitempty"` - // Optional token for the next page. - NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` -} - -func (x *ListMembersResponse) Reset() { - *x = ListMembersResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ListMembersResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListMembersResponse) ProtoMessage() {} - -func (x *ListMembersResponse) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListMembersResponse.ProtoReflect.Descriptor instead. -func (*ListMembersResponse) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{15} -} - -func (x *ListMembersResponse) GetMembers() []*Member { - if x != nil { - return x.Members - } - return nil -} - -func (x *ListMembersResponse) GetNextPageToken() string { - if x != nil { - return x.NextPageToken - } - return "" -} - -// Request to get information about a specific member. -type GetMemberRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Required. Mintter Account ID to get the information for. - AccountId string `protobuf:"bytes,1,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` -} - -func (x *GetMemberRequest) Reset() { - *x = GetMemberRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetMemberRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetMemberRequest) ProtoMessage() {} - -func (x *GetMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetMemberRequest.ProtoReflect.Descriptor instead. -func (*GetMemberRequest) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{16} -} - -func (x *GetMemberRequest) GetAccountId() string { - if x != nil { - return x.AccountId - } - return "" -} - -// Request to delete an existing member. -type DeleteMemberRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Required. Mintter Account ID of the member to be deleted. - AccountId string `protobuf:"bytes,1,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` -} - -func (x *DeleteMemberRequest) Reset() { - *x = DeleteMemberRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *DeleteMemberRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DeleteMemberRequest) ProtoMessage() {} - -func (x *DeleteMemberRequest) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[17] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DeleteMemberRequest.ProtoReflect.Descriptor instead. -func (*DeleteMemberRequest) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{17} -} - -func (x *DeleteMemberRequest) GetAccountId() string { - if x != nil { - return x.AccountId - } - return "" -} - -// Request to publish a Mintter Document on a Mintter Web Site. -type PublishDocumentRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Required. ID of the document to publish. - DocumentId string `protobuf:"bytes,1,opt,name=document_id,json=documentId,proto3" json:"document_id,omitempty"` - // Required. Specific version of the document to publish. - Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` - // Required. Path for the URL of this document on the web site. - Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` - // List of other supporting materials that are necessary - // to properly render the document being published. - // Namely, originals that are transcluded (or possibly linked) - // in the document being published. - ReferencedDocuments []*ReferencedDocument `protobuf:"bytes,4,rep,name=referenced_documents,json=referencedDocuments,proto3" json:"referenced_documents,omitempty"` -} - -func (x *PublishDocumentRequest) Reset() { - *x = PublishDocumentRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *PublishDocumentRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PublishDocumentRequest) ProtoMessage() {} - -func (x *PublishDocumentRequest) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[18] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PublishDocumentRequest.ProtoReflect.Descriptor instead. -func (*PublishDocumentRequest) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{18} -} - -func (x *PublishDocumentRequest) GetDocumentId() string { - if x != nil { - return x.DocumentId - } - return "" -} - -func (x *PublishDocumentRequest) GetVersion() string { - if x != nil { - return x.Version - } - return "" -} - -func (x *PublishDocumentRequest) GetPath() string { - if x != nil { - return x.Path - } - return "" -} - -func (x *PublishDocumentRequest) GetReferencedDocuments() []*ReferencedDocument { - if x != nil { - return x.ReferencedDocuments - } - return nil -} - -// Response when publishing a document. -type PublishDocumentResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *PublishDocumentResponse) Reset() { - *x = PublishDocumentResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *PublishDocumentResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PublishDocumentResponse) ProtoMessage() {} - -func (x *PublishDocumentResponse) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[19] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PublishDocumentResponse.ProtoReflect.Descriptor instead. -func (*PublishDocumentResponse) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{19} -} - -// Request to unpublish a document from a site. -type UnpublishDocumentRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Required. Document ID to unpublish from the web site. - DocumentId string `protobuf:"bytes,1,opt,name=document_id,json=documentId,proto3" json:"document_id,omitempty"` - // Optional. Specific version to unpublish from the web site. - // If empty, all versions matching document_id will be unpublished - Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` -} - -func (x *UnpublishDocumentRequest) Reset() { - *x = UnpublishDocumentRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *UnpublishDocumentRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UnpublishDocumentRequest) ProtoMessage() {} - -func (x *UnpublishDocumentRequest) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[20] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UnpublishDocumentRequest.ProtoReflect.Descriptor instead. -func (*UnpublishDocumentRequest) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{20} -} - -func (x *UnpublishDocumentRequest) GetDocumentId() string { - if x != nil { - return x.DocumentId - } - return "" -} - -func (x *UnpublishDocumentRequest) GetVersion() string { - if x != nil { - return x.Version - } - return "" -} - -// Response after unpublishing a web publication. -type UnpublishDocumentResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *UnpublishDocumentResponse) Reset() { - *x = UnpublishDocumentResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *UnpublishDocumentResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UnpublishDocumentResponse) ProtoMessage() {} - -func (x *UnpublishDocumentResponse) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[21] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UnpublishDocumentResponse.ProtoReflect.Descriptor instead. -func (*UnpublishDocumentResponse) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{21} -} - -// Request to list documents published on a web site. -type ListWebPublicationsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Number of items per page. - PageSize int32 `protobuf:"varint,1,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - // Token for a specific page. - PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` - // Optional. DocumentID of the publication to retrieve. - // If not provided, all publications will be returned - DocumentId string `protobuf:"bytes,3,opt,name=document_id,json=documentId,proto3" json:"document_id,omitempty"` -} - -func (x *ListWebPublicationsRequest) Reset() { - *x = ListWebPublicationsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ListWebPublicationsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListWebPublicationsRequest) ProtoMessage() {} - -func (x *ListWebPublicationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[22] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListWebPublicationsRequest.ProtoReflect.Descriptor instead. -func (*ListWebPublicationsRequest) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{22} -} - -func (x *ListWebPublicationsRequest) GetPageSize() int32 { - if x != nil { - return x.PageSize - } - return 0 -} - -func (x *ListWebPublicationsRequest) GetPageToken() string { - if x != nil { - return x.PageToken - } - return "" -} - -func (x *ListWebPublicationsRequest) GetDocumentId() string { - if x != nil { - return x.DocumentId - } - return "" -} - -// Response of all documents published on a web site. -// A single Document ID can have multiple publication records -// under different paths, and/or with different versions. -type ListWebPublicationsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Publications []*WebPublicationRecord `protobuf:"bytes,1,rep,name=publications,proto3" json:"publications,omitempty"` -} - -func (x *ListWebPublicationsResponse) Reset() { - *x = ListWebPublicationsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ListWebPublicationsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListWebPublicationsResponse) ProtoMessage() {} - -func (x *ListWebPublicationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[23] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListWebPublicationsResponse.ProtoReflect.Descriptor instead. -func (*ListWebPublicationsResponse) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{23} -} - -func (x *ListWebPublicationsResponse) GetPublications() []*WebPublicationRecord { - if x != nil { - return x.Publications - } - return nil -} - -// Invite token is produced by the owner of the site to invite new members. -type InviteToken struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // The value of the invite token. - Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` - // Timestamp after which the token will not be recognized by the site. - ExpireTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=expire_time,json=expireTime,proto3" json:"expire_time,omitempty"` -} - -func (x *InviteToken) Reset() { - *x = InviteToken{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[24] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *InviteToken) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*InviteToken) ProtoMessage() {} - -func (x *InviteToken) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[24] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use InviteToken.ProtoReflect.Descriptor instead. -func (*InviteToken) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{24} -} - -func (x *InviteToken) GetToken() string { - if x != nil { - return x.Token - } - return "" -} - -func (x *InviteToken) GetExpireTime() *timestamppb.Timestamp { - if x != nil { - return x.ExpireTime - } - return nil -} - -// Public-facing information about the Mintter Site. -type SiteInfo struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Hostname under which this site is exposed. - Hostname string `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"` - // Title of this site. - Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"` - // Description of this site. - Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` - // Mintter Account ID that is configured as the owner of this site. - Owner string `protobuf:"bytes,4,opt,name=owner,proto3" json:"owner,omitempty"` -} - -func (x *SiteInfo) Reset() { - *x = SiteInfo{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[25] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SiteInfo) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SiteInfo) ProtoMessage() {} - -func (x *SiteInfo) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[25] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SiteInfo.ProtoReflect.Descriptor instead. -func (*SiteInfo) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{25} -} - -func (x *SiteInfo) GetHostname() string { - if x != nil { - return x.Hostname - } - return "" -} - -func (x *SiteInfo) GetTitle() string { - if x != nil { - return x.Title - } - return "" -} - -func (x *SiteInfo) GetDescription() string { - if x != nil { - return x.Description - } - return "" -} - -func (x *SiteInfo) GetOwner() string { - if x != nil { - return x.Owner - } - return "" -} - -// Response for the /.well-known discovery HTTP page. -type SiteDiscoveryConfig struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Mintter Account ID of the site. - AccountId string `protobuf:"bytes,1,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` - // The PeerID of this P2P node. - PeerId string `protobuf:"bytes,2,opt,name=peer_id,json=peerId,proto3" json:"peer_id,omitempty"` - // The addresses of this site node in multiaddr format. - Addresses []string `protobuf:"bytes,3,rep,name=addresses,proto3" json:"addresses,omitempty"` -} - -func (x *SiteDiscoveryConfig) Reset() { - *x = SiteDiscoveryConfig{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[26] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *SiteDiscoveryConfig) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SiteDiscoveryConfig) ProtoMessage() {} - -func (x *SiteDiscoveryConfig) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[26] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SiteDiscoveryConfig.ProtoReflect.Descriptor instead. -func (*SiteDiscoveryConfig) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{26} -} - -func (x *SiteDiscoveryConfig) GetAccountId() string { - if x != nil { - return x.AccountId - } - return "" -} - -func (x *SiteDiscoveryConfig) GetPeerId() string { - if x != nil { - return x.PeerId - } - return "" -} - -func (x *SiteDiscoveryConfig) GetAddresses() []string { - if x != nil { - return x.Addresses - } - return nil -} - -// Description of "referenced" materials that go along -// with a Mintter Document being published on a Web Site. -type ReferencedDocument struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Required. ID of the document. - DocumentId string `protobuf:"bytes,1,opt,name=document_id,json=documentId,proto3" json:"document_id,omitempty"` - // Required. Specific version of the document. - Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` -} - -func (x *ReferencedDocument) Reset() { - *x = ReferencedDocument{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[27] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ReferencedDocument) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ReferencedDocument) ProtoMessage() {} - -func (x *ReferencedDocument) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[27] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ReferencedDocument.ProtoReflect.Descriptor instead. -func (*ReferencedDocument) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{27} -} - -func (x *ReferencedDocument) GetDocumentId() string { - if x != nil { - return x.DocumentId - } - return "" -} - -func (x *ReferencedDocument) GetVersion() string { - if x != nil { - return x.Version - } - return "" -} - -type GetPathRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Get doc by path. Empty string === home/root doc. - Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` -} - -func (x *GetPathRequest) Reset() { - *x = GetPathRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[28] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetPathRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetPathRequest) ProtoMessage() {} - -func (x *GetPathRequest) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[28] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetPathRequest.ProtoReflect.Descriptor instead. -func (*GetPathRequest) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{28} -} - -func (x *GetPathRequest) GetPath() string { - if x != nil { - return x.Path - } - return "" -} - -type GetPathResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Publication - Publication *Publication `protobuf:"bytes,1,opt,name=publication,proto3" json:"publication,omitempty"` -} - -func (x *GetPathResponse) Reset() { - *x = GetPathResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[29] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetPathResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetPathResponse) ProtoMessage() {} - -func (x *GetPathResponse) ProtoReflect() protoreflect.Message { - mi := &file_documents_v1alpha_web_publishing_proto_msgTypes[29] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetPathResponse.ProtoReflect.Descriptor instead. -func (*GetPathResponse) Descriptor() ([]byte, []int) { - return file_documents_v1alpha_web_publishing_proto_rawDescGZIP(), []int{29} -} - -func (x *GetPathResponse) GetPublication() *Publication { - if x != nil { - return x.Publication - } - return nil -} - -var File_documents_v1alpha_web_publishing_proto protoreflect.FileDescriptor - -var file_documents_v1alpha_web_publishing_proto_rawDesc = []byte{ - 0x0a, 0x26, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x2f, 0x77, 0x65, 0x62, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, - 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, - 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x1a, 0x21, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2f, 0x64, 0x6f, 0x63, 0x75, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, - 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x4f, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x53, - 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, - 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, - 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, - 0x76, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x2f, 0x0a, 0x11, 0x52, 0x65, 0x6d, - 0x6f, 0x76, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, - 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x4e, 0x0a, 0x10, 0x4c, 0x69, - 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, - 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, - 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x7c, 0x0a, 0x11, 0x4c, 0x69, - 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x3f, 0x0a, 0x05, 0x73, 0x69, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, - 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, - 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, - 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x73, 0x69, 0x74, 0x65, 0x73, - 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, - 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x5d, 0x0a, 0x20, 0x4c, 0x69, 0x73, 0x74, - 0x57, 0x65, 0x62, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, - 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x18, 0x0a, - 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x7c, 0x0a, 0x21, 0x4c, 0x69, 0x73, 0x74, 0x57, - 0x65, 0x62, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, - 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, - 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x57, 0x65, 0x62, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x68, 0x0a, 0x0a, 0x53, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x3e, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, - 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x22, - 0x9c, 0x01, 0x0a, 0x06, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x3e, 0x0a, 0x04, 0x72, 0x6f, 0x6c, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, - 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x2e, 0x52, - 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x22, 0x33, 0x0a, 0x04, 0x52, 0x6f, 0x6c, - 0x65, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x4f, 0x4c, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, - 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x44, 0x49, 0x54, 0x4f, 0x52, 0x10, 0x02, 0x22, 0x81, - 0x01, 0x0a, 0x14, 0x57, 0x65, 0x62, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, 0x63, 0x75, 0x6d, - 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x6f, - 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, - 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, - 0x74, 0x68, 0x22, 0x97, 0x01, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x76, - 0x69, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x3e, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, - 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, - 0x3b, 0x0a, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x30, 0x0a, 0x18, - 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x5b, - 0x0a, 0x19, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x04, 0x72, - 0x6f, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, - 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, - 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x22, 0x14, 0x0a, 0x12, 0x47, - 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0x4f, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, - 0x74, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, - 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x22, 0x50, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, - 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, - 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x7e, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x07, 0x6d, - 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x63, - 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4d, 0x65, 0x6d, - 0x62, 0x65, 0x72, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x26, 0x0a, 0x0f, - 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x31, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x34, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, - 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x22, 0xcd, 0x01, - 0x0a, 0x16, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, 0x63, 0x75, - 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, - 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x64, 0x0a, 0x14, 0x72, 0x65, 0x66, 0x65, 0x72, - 0x65, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, - 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, - 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x64, - 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x13, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, - 0x6e, 0x63, 0x65, 0x64, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x19, 0x0a, - 0x17, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x55, 0x0a, 0x18, 0x55, 0x6e, 0x70, 0x75, - 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x6f, 0x63, 0x75, 0x6d, - 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, - 0x1b, 0x0a, 0x19, 0x55, 0x6e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x6f, 0x63, 0x75, - 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x79, 0x0a, 0x1a, - 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x62, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, - 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, - 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, - 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, - 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x6f, 0x63, - 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x76, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x57, - 0x65, 0x62, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x63, - 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x57, 0x65, 0x62, - 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, - 0x64, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, - 0x60, 0x0a, 0x0b, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, - 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x3b, 0x0a, 0x0b, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x54, 0x69, 0x6d, - 0x65, 0x22, 0x74, 0x0a, 0x08, 0x53, 0x69, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x0a, - 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, - 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, - 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x22, 0x6b, 0x0a, 0x13, 0x53, 0x69, 0x74, 0x65, 0x44, - 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1d, - 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, - 0x07, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x70, 0x65, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x65, 0x73, 0x22, 0x4f, 0x0a, 0x12, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, - 0x65, 0x64, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, - 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x24, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x50, 0x61, 0x74, 0x68, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x5f, 0x0a, 0x0f, 0x47, - 0x65, 0x74, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, - 0x0a, 0x0b, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, - 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x0b, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x32, 0xdd, 0x03, 0x0a, - 0x0d, 0x57, 0x65, 0x62, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x12, 0x63, - 0x0a, 0x07, 0x41, 0x64, 0x64, 0x53, 0x69, 0x74, 0x65, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, - 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x53, 0x69, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, - 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x56, 0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x53, 0x69, 0x74, - 0x65, 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, - 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x6e, 0x0a, 0x09, 0x4c, - 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, 0x65, 0x73, 0x12, 0x2f, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, - 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, 0x74, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, - 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x69, - 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x9e, 0x01, 0x0a, 0x19, - 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x62, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x3f, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, - 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, - 0x62, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x6f, - 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x40, 0x2e, 0x63, 0x6f, 0x6d, - 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, - 0x65, 0x62, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xa4, 0x0a, 0x0a, - 0x07, 0x57, 0x65, 0x62, 0x53, 0x69, 0x74, 0x65, 0x12, 0x78, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x37, 0x2e, - 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, - 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x12, 0x86, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x49, 0x6e, 0x76, - 0x69, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x37, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, - 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x49, - 0x6e, 0x76, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x38, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, - 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x2e, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x49, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x69, 0x0a, 0x0b, 0x47, - 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6d, - 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, - 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, - 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, 0x69, - 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x6f, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x53, 0x69, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, - 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, - 0x69, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, - 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, - 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x53, - 0x69, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x74, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4d, - 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, - 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, - 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x63, 0x0a, - 0x09, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2f, 0x2e, 0x63, 0x6f, 0x6d, - 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x63, 0x6f, - 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4d, 0x65, 0x6d, 0x62, - 0x65, 0x72, 0x12, 0x5a, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x65, 0x6d, 0x62, - 0x65, 0x72, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, - 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x80, - 0x01, 0x0a, 0x0f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, - 0x6e, 0x74, 0x12, 0x35, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, - 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, - 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, - 0x68, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x86, 0x01, 0x0a, 0x11, 0x55, 0x6e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, - 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x37, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, - 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x6e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, - 0x68, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x38, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, - 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x2e, 0x55, 0x6e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x13, 0x4c, - 0x69, 0x73, 0x74, 0x57, 0x65, 0x62, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x39, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, - 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x65, 0x62, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3a, 0x2e, - 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x57, 0x65, 0x62, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x07, 0x47, 0x65, 0x74, - 0x50, 0x61, 0x74, 0x68, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, - 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, - 0x72, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x42, 0x36, 0x5a, 0x34, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2f, 0x62, - 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x67, 0x65, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, - 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x3b, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, -} - -var ( - file_documents_v1alpha_web_publishing_proto_rawDescOnce sync.Once - file_documents_v1alpha_web_publishing_proto_rawDescData = file_documents_v1alpha_web_publishing_proto_rawDesc -) - -func file_documents_v1alpha_web_publishing_proto_rawDescGZIP() []byte { - file_documents_v1alpha_web_publishing_proto_rawDescOnce.Do(func() { - file_documents_v1alpha_web_publishing_proto_rawDescData = protoimpl.X.CompressGZIP(file_documents_v1alpha_web_publishing_proto_rawDescData) - }) - return file_documents_v1alpha_web_publishing_proto_rawDescData -} - -var file_documents_v1alpha_web_publishing_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_documents_v1alpha_web_publishing_proto_msgTypes = make([]protoimpl.MessageInfo, 30) -var file_documents_v1alpha_web_publishing_proto_goTypes = []interface{}{ - (Member_Role)(0), // 0: com.mintter.documents.v1alpha.Member.Role - (*AddSiteRequest)(nil), // 1: com.mintter.documents.v1alpha.AddSiteRequest - (*RemoveSiteRequest)(nil), // 2: com.mintter.documents.v1alpha.RemoveSiteRequest - (*ListSitesRequest)(nil), // 3: com.mintter.documents.v1alpha.ListSitesRequest - (*ListSitesResponse)(nil), // 4: com.mintter.documents.v1alpha.ListSitesResponse - (*ListWebPublicationRecordsRequest)(nil), // 5: com.mintter.documents.v1alpha.ListWebPublicationRecordsRequest - (*ListWebPublicationRecordsResponse)(nil), // 6: com.mintter.documents.v1alpha.ListWebPublicationRecordsResponse - (*SiteConfig)(nil), // 7: com.mintter.documents.v1alpha.SiteConfig - (*Member)(nil), // 8: com.mintter.documents.v1alpha.Member - (*WebPublicationRecord)(nil), // 9: com.mintter.documents.v1alpha.WebPublicationRecord - (*CreateInviteTokenRequest)(nil), // 10: com.mintter.documents.v1alpha.CreateInviteTokenRequest - (*RedeemInviteTokenRequest)(nil), // 11: com.mintter.documents.v1alpha.RedeemInviteTokenRequest - (*RedeemInviteTokenResponse)(nil), // 12: com.mintter.documents.v1alpha.RedeemInviteTokenResponse - (*GetSiteInfoRequest)(nil), // 13: com.mintter.documents.v1alpha.GetSiteInfoRequest - (*UpdateSiteInfoRequest)(nil), // 14: com.mintter.documents.v1alpha.UpdateSiteInfoRequest - (*ListMembersRequest)(nil), // 15: com.mintter.documents.v1alpha.ListMembersRequest - (*ListMembersResponse)(nil), // 16: com.mintter.documents.v1alpha.ListMembersResponse - (*GetMemberRequest)(nil), // 17: com.mintter.documents.v1alpha.GetMemberRequest - (*DeleteMemberRequest)(nil), // 18: com.mintter.documents.v1alpha.DeleteMemberRequest - (*PublishDocumentRequest)(nil), // 19: com.mintter.documents.v1alpha.PublishDocumentRequest - (*PublishDocumentResponse)(nil), // 20: com.mintter.documents.v1alpha.PublishDocumentResponse - (*UnpublishDocumentRequest)(nil), // 21: com.mintter.documents.v1alpha.UnpublishDocumentRequest - (*UnpublishDocumentResponse)(nil), // 22: com.mintter.documents.v1alpha.UnpublishDocumentResponse - (*ListWebPublicationsRequest)(nil), // 23: com.mintter.documents.v1alpha.ListWebPublicationsRequest - (*ListWebPublicationsResponse)(nil), // 24: com.mintter.documents.v1alpha.ListWebPublicationsResponse - (*InviteToken)(nil), // 25: com.mintter.documents.v1alpha.InviteToken - (*SiteInfo)(nil), // 26: com.mintter.documents.v1alpha.SiteInfo - (*SiteDiscoveryConfig)(nil), // 27: com.mintter.documents.v1alpha.SiteDiscoveryConfig - (*ReferencedDocument)(nil), // 28: com.mintter.documents.v1alpha.ReferencedDocument - (*GetPathRequest)(nil), // 29: com.mintter.documents.v1alpha.GetPathRequest - (*GetPathResponse)(nil), // 30: com.mintter.documents.v1alpha.GetPathResponse - (*timestamppb.Timestamp)(nil), // 31: google.protobuf.Timestamp - (*Publication)(nil), // 32: com.mintter.documents.v1alpha.Publication - (*emptypb.Empty)(nil), // 33: google.protobuf.Empty -} -var file_documents_v1alpha_web_publishing_proto_depIdxs = []int32{ - 7, // 0: com.mintter.documents.v1alpha.ListSitesResponse.sites:type_name -> com.mintter.documents.v1alpha.SiteConfig - 9, // 1: com.mintter.documents.v1alpha.ListWebPublicationRecordsResponse.publications:type_name -> com.mintter.documents.v1alpha.WebPublicationRecord - 0, // 2: com.mintter.documents.v1alpha.SiteConfig.role:type_name -> com.mintter.documents.v1alpha.Member.Role - 0, // 3: com.mintter.documents.v1alpha.Member.role:type_name -> com.mintter.documents.v1alpha.Member.Role - 0, // 4: com.mintter.documents.v1alpha.CreateInviteTokenRequest.role:type_name -> com.mintter.documents.v1alpha.Member.Role - 31, // 5: com.mintter.documents.v1alpha.CreateInviteTokenRequest.expire_time:type_name -> google.protobuf.Timestamp - 0, // 6: com.mintter.documents.v1alpha.RedeemInviteTokenResponse.role:type_name -> com.mintter.documents.v1alpha.Member.Role - 8, // 7: com.mintter.documents.v1alpha.ListMembersResponse.members:type_name -> com.mintter.documents.v1alpha.Member - 28, // 8: com.mintter.documents.v1alpha.PublishDocumentRequest.referenced_documents:type_name -> com.mintter.documents.v1alpha.ReferencedDocument - 9, // 9: com.mintter.documents.v1alpha.ListWebPublicationsResponse.publications:type_name -> com.mintter.documents.v1alpha.WebPublicationRecord - 31, // 10: com.mintter.documents.v1alpha.InviteToken.expire_time:type_name -> google.protobuf.Timestamp - 32, // 11: com.mintter.documents.v1alpha.GetPathResponse.publication:type_name -> com.mintter.documents.v1alpha.Publication - 1, // 12: com.mintter.documents.v1alpha.WebPublishing.AddSite:input_type -> com.mintter.documents.v1alpha.AddSiteRequest - 2, // 13: com.mintter.documents.v1alpha.WebPublishing.RemoveSite:input_type -> com.mintter.documents.v1alpha.RemoveSiteRequest - 3, // 14: com.mintter.documents.v1alpha.WebPublishing.ListSites:input_type -> com.mintter.documents.v1alpha.ListSitesRequest - 5, // 15: com.mintter.documents.v1alpha.WebPublishing.ListWebPublicationRecords:input_type -> com.mintter.documents.v1alpha.ListWebPublicationRecordsRequest - 10, // 16: com.mintter.documents.v1alpha.WebSite.CreateInviteToken:input_type -> com.mintter.documents.v1alpha.CreateInviteTokenRequest - 11, // 17: com.mintter.documents.v1alpha.WebSite.RedeemInviteToken:input_type -> com.mintter.documents.v1alpha.RedeemInviteTokenRequest - 13, // 18: com.mintter.documents.v1alpha.WebSite.GetSiteInfo:input_type -> com.mintter.documents.v1alpha.GetSiteInfoRequest - 14, // 19: com.mintter.documents.v1alpha.WebSite.UpdateSiteInfo:input_type -> com.mintter.documents.v1alpha.UpdateSiteInfoRequest - 15, // 20: com.mintter.documents.v1alpha.WebSite.ListMembers:input_type -> com.mintter.documents.v1alpha.ListMembersRequest - 17, // 21: com.mintter.documents.v1alpha.WebSite.GetMember:input_type -> com.mintter.documents.v1alpha.GetMemberRequest - 18, // 22: com.mintter.documents.v1alpha.WebSite.DeleteMember:input_type -> com.mintter.documents.v1alpha.DeleteMemberRequest - 19, // 23: com.mintter.documents.v1alpha.WebSite.PublishDocument:input_type -> com.mintter.documents.v1alpha.PublishDocumentRequest - 21, // 24: com.mintter.documents.v1alpha.WebSite.UnpublishDocument:input_type -> com.mintter.documents.v1alpha.UnpublishDocumentRequest - 23, // 25: com.mintter.documents.v1alpha.WebSite.ListWebPublications:input_type -> com.mintter.documents.v1alpha.ListWebPublicationsRequest - 29, // 26: com.mintter.documents.v1alpha.WebSite.GetPath:input_type -> com.mintter.documents.v1alpha.GetPathRequest - 7, // 27: com.mintter.documents.v1alpha.WebPublishing.AddSite:output_type -> com.mintter.documents.v1alpha.SiteConfig - 33, // 28: com.mintter.documents.v1alpha.WebPublishing.RemoveSite:output_type -> google.protobuf.Empty - 4, // 29: com.mintter.documents.v1alpha.WebPublishing.ListSites:output_type -> com.mintter.documents.v1alpha.ListSitesResponse - 6, // 30: com.mintter.documents.v1alpha.WebPublishing.ListWebPublicationRecords:output_type -> com.mintter.documents.v1alpha.ListWebPublicationRecordsResponse - 25, // 31: com.mintter.documents.v1alpha.WebSite.CreateInviteToken:output_type -> com.mintter.documents.v1alpha.InviteToken - 12, // 32: com.mintter.documents.v1alpha.WebSite.RedeemInviteToken:output_type -> com.mintter.documents.v1alpha.RedeemInviteTokenResponse - 26, // 33: com.mintter.documents.v1alpha.WebSite.GetSiteInfo:output_type -> com.mintter.documents.v1alpha.SiteInfo - 26, // 34: com.mintter.documents.v1alpha.WebSite.UpdateSiteInfo:output_type -> com.mintter.documents.v1alpha.SiteInfo - 16, // 35: com.mintter.documents.v1alpha.WebSite.ListMembers:output_type -> com.mintter.documents.v1alpha.ListMembersResponse - 8, // 36: com.mintter.documents.v1alpha.WebSite.GetMember:output_type -> com.mintter.documents.v1alpha.Member - 33, // 37: com.mintter.documents.v1alpha.WebSite.DeleteMember:output_type -> google.protobuf.Empty - 20, // 38: com.mintter.documents.v1alpha.WebSite.PublishDocument:output_type -> com.mintter.documents.v1alpha.PublishDocumentResponse - 22, // 39: com.mintter.documents.v1alpha.WebSite.UnpublishDocument:output_type -> com.mintter.documents.v1alpha.UnpublishDocumentResponse - 24, // 40: com.mintter.documents.v1alpha.WebSite.ListWebPublications:output_type -> com.mintter.documents.v1alpha.ListWebPublicationsResponse - 30, // 41: com.mintter.documents.v1alpha.WebSite.GetPath:output_type -> com.mintter.documents.v1alpha.GetPathResponse - 27, // [27:42] is the sub-list for method output_type - 12, // [12:27] is the sub-list for method input_type - 12, // [12:12] is the sub-list for extension type_name - 12, // [12:12] is the sub-list for extension extendee - 0, // [0:12] is the sub-list for field type_name -} - -func init() { file_documents_v1alpha_web_publishing_proto_init() } -func file_documents_v1alpha_web_publishing_proto_init() { - if File_documents_v1alpha_web_publishing_proto != nil { - return - } - file_documents_v1alpha_documents_proto_init() - if !protoimpl.UnsafeEnabled { - file_documents_v1alpha_web_publishing_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddSiteRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RemoveSiteRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListSitesRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListSitesResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListWebPublicationRecordsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListWebPublicationRecordsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SiteConfig); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Member); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WebPublicationRecord); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateInviteTokenRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RedeemInviteTokenRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RedeemInviteTokenResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetSiteInfoRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateSiteInfoRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListMembersRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListMembersResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetMemberRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteMemberRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PublishDocumentRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PublishDocumentResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UnpublishDocumentRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UnpublishDocumentResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListWebPublicationsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListWebPublicationsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*InviteToken); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SiteInfo); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SiteDiscoveryConfig); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ReferencedDocument); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetPathRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_documents_v1alpha_web_publishing_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetPathResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_documents_v1alpha_web_publishing_proto_rawDesc, - NumEnums: 1, - NumMessages: 30, - NumExtensions: 0, - NumServices: 2, - }, - GoTypes: file_documents_v1alpha_web_publishing_proto_goTypes, - DependencyIndexes: file_documents_v1alpha_web_publishing_proto_depIdxs, - EnumInfos: file_documents_v1alpha_web_publishing_proto_enumTypes, - MessageInfos: file_documents_v1alpha_web_publishing_proto_msgTypes, - }.Build() - File_documents_v1alpha_web_publishing_proto = out.File - file_documents_v1alpha_web_publishing_proto_rawDesc = nil - file_documents_v1alpha_web_publishing_proto_goTypes = nil - file_documents_v1alpha_web_publishing_proto_depIdxs = nil -} diff --git a/backend/genproto/documents/v1alpha/web_publishing_grpc.pb.go b/backend/genproto/documents/v1alpha/web_publishing_grpc.pb.go deleted file mode 100644 index 5f8563b8e2..0000000000 --- a/backend/genproto/documents/v1alpha/web_publishing_grpc.pb.go +++ /dev/null @@ -1,698 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.21.12 -// source: documents/v1alpha/web_publishing.proto - -package documents - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - emptypb "google.golang.org/protobuf/types/known/emptypb" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -// WebPublishingClient is the client API for WebPublishing service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type WebPublishingClient interface { - // Adds a site configuration to the local app backend. - AddSite(ctx context.Context, in *AddSiteRequest, opts ...grpc.CallOption) (*SiteConfig, error) - // Removes site configuration from the local app backend. - RemoveSite(ctx context.Context, in *RemoveSiteRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) - // Lists configured sites. - ListSites(ctx context.Context, in *ListSitesRequest, opts ...grpc.CallOption) (*ListSitesResponse, error) - // Lists web publication records for a given document among the configured web sites. - ListWebPublicationRecords(ctx context.Context, in *ListWebPublicationRecordsRequest, opts ...grpc.CallOption) (*ListWebPublicationRecordsResponse, error) -} - -type webPublishingClient struct { - cc grpc.ClientConnInterface -} - -func NewWebPublishingClient(cc grpc.ClientConnInterface) WebPublishingClient { - return &webPublishingClient{cc} -} - -func (c *webPublishingClient) AddSite(ctx context.Context, in *AddSiteRequest, opts ...grpc.CallOption) (*SiteConfig, error) { - out := new(SiteConfig) - err := c.cc.Invoke(ctx, "/com.mintter.documents.v1alpha.WebPublishing/AddSite", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *webPublishingClient) RemoveSite(ctx context.Context, in *RemoveSiteRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, "/com.mintter.documents.v1alpha.WebPublishing/RemoveSite", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *webPublishingClient) ListSites(ctx context.Context, in *ListSitesRequest, opts ...grpc.CallOption) (*ListSitesResponse, error) { - out := new(ListSitesResponse) - err := c.cc.Invoke(ctx, "/com.mintter.documents.v1alpha.WebPublishing/ListSites", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *webPublishingClient) ListWebPublicationRecords(ctx context.Context, in *ListWebPublicationRecordsRequest, opts ...grpc.CallOption) (*ListWebPublicationRecordsResponse, error) { - out := new(ListWebPublicationRecordsResponse) - err := c.cc.Invoke(ctx, "/com.mintter.documents.v1alpha.WebPublishing/ListWebPublicationRecords", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// WebPublishingServer is the server API for WebPublishing service. -// All implementations should embed UnimplementedWebPublishingServer -// for forward compatibility -type WebPublishingServer interface { - // Adds a site configuration to the local app backend. - AddSite(context.Context, *AddSiteRequest) (*SiteConfig, error) - // Removes site configuration from the local app backend. - RemoveSite(context.Context, *RemoveSiteRequest) (*emptypb.Empty, error) - // Lists configured sites. - ListSites(context.Context, *ListSitesRequest) (*ListSitesResponse, error) - // Lists web publication records for a given document among the configured web sites. - ListWebPublicationRecords(context.Context, *ListWebPublicationRecordsRequest) (*ListWebPublicationRecordsResponse, error) -} - -// UnimplementedWebPublishingServer should be embedded to have forward compatible implementations. -type UnimplementedWebPublishingServer struct { -} - -func (UnimplementedWebPublishingServer) AddSite(context.Context, *AddSiteRequest) (*SiteConfig, error) { - return nil, status.Errorf(codes.Unimplemented, "method AddSite not implemented") -} -func (UnimplementedWebPublishingServer) RemoveSite(context.Context, *RemoveSiteRequest) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method RemoveSite not implemented") -} -func (UnimplementedWebPublishingServer) ListSites(context.Context, *ListSitesRequest) (*ListSitesResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ListSites not implemented") -} -func (UnimplementedWebPublishingServer) ListWebPublicationRecords(context.Context, *ListWebPublicationRecordsRequest) (*ListWebPublicationRecordsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ListWebPublicationRecords not implemented") -} - -// UnsafeWebPublishingServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to WebPublishingServer will -// result in compilation errors. -type UnsafeWebPublishingServer interface { - mustEmbedUnimplementedWebPublishingServer() -} - -func RegisterWebPublishingServer(s grpc.ServiceRegistrar, srv WebPublishingServer) { - s.RegisterService(&WebPublishing_ServiceDesc, srv) -} - -func _WebPublishing_AddSite_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(AddSiteRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WebPublishingServer).AddSite(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.documents.v1alpha.WebPublishing/AddSite", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WebPublishingServer).AddSite(ctx, req.(*AddSiteRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _WebPublishing_RemoveSite_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(RemoveSiteRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WebPublishingServer).RemoveSite(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.documents.v1alpha.WebPublishing/RemoveSite", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WebPublishingServer).RemoveSite(ctx, req.(*RemoveSiteRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _WebPublishing_ListSites_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListSitesRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WebPublishingServer).ListSites(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.documents.v1alpha.WebPublishing/ListSites", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WebPublishingServer).ListSites(ctx, req.(*ListSitesRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _WebPublishing_ListWebPublicationRecords_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListWebPublicationRecordsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WebPublishingServer).ListWebPublicationRecords(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.documents.v1alpha.WebPublishing/ListWebPublicationRecords", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WebPublishingServer).ListWebPublicationRecords(ctx, req.(*ListWebPublicationRecordsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// WebPublishing_ServiceDesc is the grpc.ServiceDesc for WebPublishing service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var WebPublishing_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "com.mintter.documents.v1alpha.WebPublishing", - HandlerType: (*WebPublishingServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "AddSite", - Handler: _WebPublishing_AddSite_Handler, - }, - { - MethodName: "RemoveSite", - Handler: _WebPublishing_RemoveSite_Handler, - }, - { - MethodName: "ListSites", - Handler: _WebPublishing_ListSites_Handler, - }, - { - MethodName: "ListWebPublicationRecords", - Handler: _WebPublishing_ListWebPublicationRecords_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "documents/v1alpha/web_publishing.proto", -} - -// WebSiteClient is the client API for WebSite service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type WebSiteClient interface { - // Creates a new invite token for registering a new member. - // Protected. Must require authentication. - CreateInviteToken(ctx context.Context, in *CreateInviteTokenRequest, opts ...grpc.CallOption) (*InviteToken, error) - // Redeems a previously created invite token to register a new member. - RedeemInviteToken(ctx context.Context, in *RedeemInviteTokenRequest, opts ...grpc.CallOption) (*RedeemInviteTokenResponse, error) - // Gets public-facing site information. - GetSiteInfo(ctx context.Context, in *GetSiteInfoRequest, opts ...grpc.CallOption) (*SiteInfo, error) - // Updates public-facing site information. Doesn't support partial updates, - // hence all the fields must be provided. - // Protected. Must require authentication. - UpdateSiteInfo(ctx context.Context, in *UpdateSiteInfoRequest, opts ...grpc.CallOption) (*SiteInfo, error) - // Lists registered members on the site. - // May be protected or public depending on the privacy policies of the web site. - ListMembers(ctx context.Context, in *ListMembersRequest, opts ...grpc.CallOption) (*ListMembersResponse, error) - // Gets information about a specific member. - // May be protected or public depending on the privacy policies of the web site. - GetMember(ctx context.Context, in *GetMemberRequest, opts ...grpc.CallOption) (*Member, error) - // Deletes an existing member. - // Protected. Must require authentication. - DeleteMember(ctx context.Context, in *DeleteMemberRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) - // Publishes a Mintter Document on the remote web server along with any supporting materials. - PublishDocument(ctx context.Context, in *PublishDocumentRequest, opts ...grpc.CallOption) (*PublishDocumentResponse, error) - // Unpublishes a previously published Document. - UnpublishDocument(ctx context.Context, in *UnpublishDocumentRequest, opts ...grpc.CallOption) (*UnpublishDocumentResponse, error) - // list all the published documents - ListWebPublications(ctx context.Context, in *ListWebPublicationsRequest, opts ...grpc.CallOption) (*ListWebPublicationsResponse, error) - // Get the document published at a given path. - GetPath(ctx context.Context, in *GetPathRequest, opts ...grpc.CallOption) (*GetPathResponse, error) -} - -type webSiteClient struct { - cc grpc.ClientConnInterface -} - -func NewWebSiteClient(cc grpc.ClientConnInterface) WebSiteClient { - return &webSiteClient{cc} -} - -func (c *webSiteClient) CreateInviteToken(ctx context.Context, in *CreateInviteTokenRequest, opts ...grpc.CallOption) (*InviteToken, error) { - out := new(InviteToken) - err := c.cc.Invoke(ctx, "/com.mintter.documents.v1alpha.WebSite/CreateInviteToken", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *webSiteClient) RedeemInviteToken(ctx context.Context, in *RedeemInviteTokenRequest, opts ...grpc.CallOption) (*RedeemInviteTokenResponse, error) { - out := new(RedeemInviteTokenResponse) - err := c.cc.Invoke(ctx, "/com.mintter.documents.v1alpha.WebSite/RedeemInviteToken", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *webSiteClient) GetSiteInfo(ctx context.Context, in *GetSiteInfoRequest, opts ...grpc.CallOption) (*SiteInfo, error) { - out := new(SiteInfo) - err := c.cc.Invoke(ctx, "/com.mintter.documents.v1alpha.WebSite/GetSiteInfo", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *webSiteClient) UpdateSiteInfo(ctx context.Context, in *UpdateSiteInfoRequest, opts ...grpc.CallOption) (*SiteInfo, error) { - out := new(SiteInfo) - err := c.cc.Invoke(ctx, "/com.mintter.documents.v1alpha.WebSite/UpdateSiteInfo", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *webSiteClient) ListMembers(ctx context.Context, in *ListMembersRequest, opts ...grpc.CallOption) (*ListMembersResponse, error) { - out := new(ListMembersResponse) - err := c.cc.Invoke(ctx, "/com.mintter.documents.v1alpha.WebSite/ListMembers", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *webSiteClient) GetMember(ctx context.Context, in *GetMemberRequest, opts ...grpc.CallOption) (*Member, error) { - out := new(Member) - err := c.cc.Invoke(ctx, "/com.mintter.documents.v1alpha.WebSite/GetMember", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *webSiteClient) DeleteMember(ctx context.Context, in *DeleteMemberRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, "/com.mintter.documents.v1alpha.WebSite/DeleteMember", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *webSiteClient) PublishDocument(ctx context.Context, in *PublishDocumentRequest, opts ...grpc.CallOption) (*PublishDocumentResponse, error) { - out := new(PublishDocumentResponse) - err := c.cc.Invoke(ctx, "/com.mintter.documents.v1alpha.WebSite/PublishDocument", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *webSiteClient) UnpublishDocument(ctx context.Context, in *UnpublishDocumentRequest, opts ...grpc.CallOption) (*UnpublishDocumentResponse, error) { - out := new(UnpublishDocumentResponse) - err := c.cc.Invoke(ctx, "/com.mintter.documents.v1alpha.WebSite/UnpublishDocument", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *webSiteClient) ListWebPublications(ctx context.Context, in *ListWebPublicationsRequest, opts ...grpc.CallOption) (*ListWebPublicationsResponse, error) { - out := new(ListWebPublicationsResponse) - err := c.cc.Invoke(ctx, "/com.mintter.documents.v1alpha.WebSite/ListWebPublications", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *webSiteClient) GetPath(ctx context.Context, in *GetPathRequest, opts ...grpc.CallOption) (*GetPathResponse, error) { - out := new(GetPathResponse) - err := c.cc.Invoke(ctx, "/com.mintter.documents.v1alpha.WebSite/GetPath", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// WebSiteServer is the server API for WebSite service. -// All implementations should embed UnimplementedWebSiteServer -// for forward compatibility -type WebSiteServer interface { - // Creates a new invite token for registering a new member. - // Protected. Must require authentication. - CreateInviteToken(context.Context, *CreateInviteTokenRequest) (*InviteToken, error) - // Redeems a previously created invite token to register a new member. - RedeemInviteToken(context.Context, *RedeemInviteTokenRequest) (*RedeemInviteTokenResponse, error) - // Gets public-facing site information. - GetSiteInfo(context.Context, *GetSiteInfoRequest) (*SiteInfo, error) - // Updates public-facing site information. Doesn't support partial updates, - // hence all the fields must be provided. - // Protected. Must require authentication. - UpdateSiteInfo(context.Context, *UpdateSiteInfoRequest) (*SiteInfo, error) - // Lists registered members on the site. - // May be protected or public depending on the privacy policies of the web site. - ListMembers(context.Context, *ListMembersRequest) (*ListMembersResponse, error) - // Gets information about a specific member. - // May be protected or public depending on the privacy policies of the web site. - GetMember(context.Context, *GetMemberRequest) (*Member, error) - // Deletes an existing member. - // Protected. Must require authentication. - DeleteMember(context.Context, *DeleteMemberRequest) (*emptypb.Empty, error) - // Publishes a Mintter Document on the remote web server along with any supporting materials. - PublishDocument(context.Context, *PublishDocumentRequest) (*PublishDocumentResponse, error) - // Unpublishes a previously published Document. - UnpublishDocument(context.Context, *UnpublishDocumentRequest) (*UnpublishDocumentResponse, error) - // list all the published documents - ListWebPublications(context.Context, *ListWebPublicationsRequest) (*ListWebPublicationsResponse, error) - // Get the document published at a given path. - GetPath(context.Context, *GetPathRequest) (*GetPathResponse, error) -} - -// UnimplementedWebSiteServer should be embedded to have forward compatible implementations. -type UnimplementedWebSiteServer struct { -} - -func (UnimplementedWebSiteServer) CreateInviteToken(context.Context, *CreateInviteTokenRequest) (*InviteToken, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateInviteToken not implemented") -} -func (UnimplementedWebSiteServer) RedeemInviteToken(context.Context, *RedeemInviteTokenRequest) (*RedeemInviteTokenResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method RedeemInviteToken not implemented") -} -func (UnimplementedWebSiteServer) GetSiteInfo(context.Context, *GetSiteInfoRequest) (*SiteInfo, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetSiteInfo not implemented") -} -func (UnimplementedWebSiteServer) UpdateSiteInfo(context.Context, *UpdateSiteInfoRequest) (*SiteInfo, error) { - return nil, status.Errorf(codes.Unimplemented, "method UpdateSiteInfo not implemented") -} -func (UnimplementedWebSiteServer) ListMembers(context.Context, *ListMembersRequest) (*ListMembersResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ListMembers not implemented") -} -func (UnimplementedWebSiteServer) GetMember(context.Context, *GetMemberRequest) (*Member, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetMember not implemented") -} -func (UnimplementedWebSiteServer) DeleteMember(context.Context, *DeleteMemberRequest) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method DeleteMember not implemented") -} -func (UnimplementedWebSiteServer) PublishDocument(context.Context, *PublishDocumentRequest) (*PublishDocumentResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method PublishDocument not implemented") -} -func (UnimplementedWebSiteServer) UnpublishDocument(context.Context, *UnpublishDocumentRequest) (*UnpublishDocumentResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method UnpublishDocument not implemented") -} -func (UnimplementedWebSiteServer) ListWebPublications(context.Context, *ListWebPublicationsRequest) (*ListWebPublicationsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ListWebPublications not implemented") -} -func (UnimplementedWebSiteServer) GetPath(context.Context, *GetPathRequest) (*GetPathResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetPath not implemented") -} - -// UnsafeWebSiteServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to WebSiteServer will -// result in compilation errors. -type UnsafeWebSiteServer interface { - mustEmbedUnimplementedWebSiteServer() -} - -func RegisterWebSiteServer(s grpc.ServiceRegistrar, srv WebSiteServer) { - s.RegisterService(&WebSite_ServiceDesc, srv) -} - -func _WebSite_CreateInviteToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateInviteTokenRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WebSiteServer).CreateInviteToken(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.documents.v1alpha.WebSite/CreateInviteToken", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WebSiteServer).CreateInviteToken(ctx, req.(*CreateInviteTokenRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _WebSite_RedeemInviteToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(RedeemInviteTokenRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WebSiteServer).RedeemInviteToken(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.documents.v1alpha.WebSite/RedeemInviteToken", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WebSiteServer).RedeemInviteToken(ctx, req.(*RedeemInviteTokenRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _WebSite_GetSiteInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetSiteInfoRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WebSiteServer).GetSiteInfo(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.documents.v1alpha.WebSite/GetSiteInfo", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WebSiteServer).GetSiteInfo(ctx, req.(*GetSiteInfoRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _WebSite_UpdateSiteInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpdateSiteInfoRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WebSiteServer).UpdateSiteInfo(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.documents.v1alpha.WebSite/UpdateSiteInfo", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WebSiteServer).UpdateSiteInfo(ctx, req.(*UpdateSiteInfoRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _WebSite_ListMembers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListMembersRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WebSiteServer).ListMembers(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.documents.v1alpha.WebSite/ListMembers", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WebSiteServer).ListMembers(ctx, req.(*ListMembersRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _WebSite_GetMember_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetMemberRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WebSiteServer).GetMember(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.documents.v1alpha.WebSite/GetMember", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WebSiteServer).GetMember(ctx, req.(*GetMemberRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _WebSite_DeleteMember_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DeleteMemberRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WebSiteServer).DeleteMember(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.documents.v1alpha.WebSite/DeleteMember", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WebSiteServer).DeleteMember(ctx, req.(*DeleteMemberRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _WebSite_PublishDocument_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(PublishDocumentRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WebSiteServer).PublishDocument(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.documents.v1alpha.WebSite/PublishDocument", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WebSiteServer).PublishDocument(ctx, req.(*PublishDocumentRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _WebSite_UnpublishDocument_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UnpublishDocumentRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WebSiteServer).UnpublishDocument(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.documents.v1alpha.WebSite/UnpublishDocument", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WebSiteServer).UnpublishDocument(ctx, req.(*UnpublishDocumentRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _WebSite_ListWebPublications_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListWebPublicationsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WebSiteServer).ListWebPublications(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.documents.v1alpha.WebSite/ListWebPublications", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WebSiteServer).ListWebPublications(ctx, req.(*ListWebPublicationsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _WebSite_GetPath_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetPathRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(WebSiteServer).GetPath(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.documents.v1alpha.WebSite/GetPath", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(WebSiteServer).GetPath(ctx, req.(*GetPathRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// WebSite_ServiceDesc is the grpc.ServiceDesc for WebSite service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var WebSite_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "com.mintter.documents.v1alpha.WebSite", - HandlerType: (*WebSiteServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "CreateInviteToken", - Handler: _WebSite_CreateInviteToken_Handler, - }, - { - MethodName: "RedeemInviteToken", - Handler: _WebSite_RedeemInviteToken_Handler, - }, - { - MethodName: "GetSiteInfo", - Handler: _WebSite_GetSiteInfo_Handler, - }, - { - MethodName: "UpdateSiteInfo", - Handler: _WebSite_UpdateSiteInfo_Handler, - }, - { - MethodName: "ListMembers", - Handler: _WebSite_ListMembers_Handler, - }, - { - MethodName: "GetMember", - Handler: _WebSite_GetMember_Handler, - }, - { - MethodName: "DeleteMember", - Handler: _WebSite_DeleteMember_Handler, - }, - { - MethodName: "PublishDocument", - Handler: _WebSite_PublishDocument_Handler, - }, - { - MethodName: "UnpublishDocument", - Handler: _WebSite_UnpublishDocument_Handler, - }, - { - MethodName: "ListWebPublications", - Handler: _WebSite_ListWebPublications_Handler, - }, - { - MethodName: "GetPath", - Handler: _WebSite_GetPath_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "documents/v1alpha/web_publishing.proto", -} diff --git a/backend/genproto/entities/v1alpha/entities.pb.go b/backend/genproto/entities/v1alpha/entities.pb.go index 7fbdb64ddc..9641c96539 100644 --- a/backend/genproto/entities/v1alpha/entities.pb.go +++ b/backend/genproto/entities/v1alpha/entities.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.21.12 // source: entities/v1alpha/entities.proto diff --git a/backend/genproto/groups/v1alpha/groups.pb.go b/backend/genproto/groups/v1alpha/groups.pb.go index 2aa05549e4..3f0e696b32 100644 --- a/backend/genproto/groups/v1alpha/groups.pb.go +++ b/backend/genproto/groups/v1alpha/groups.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.21.12 // source: groups/v1alpha/groups.proto @@ -88,6 +88,9 @@ type CreateGroupRequest struct { // Optional. List of initial members for the new group. // Members can also be managed with separate requests after group is already created. Members map[string]Role `protobuf:"bytes,3,rep,name=members,proto3" json:"members,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3,enum=com.mintter.groups.v1alpha.Role"` + // Optional. Secret setup URL that is provided during site server deployment. + // I.e. a place on the Web where this group has to be published. + SiteSetupUrl string `protobuf:"bytes,4,opt,name=site_setup_url,json=siteSetupUrl,proto3" json:"site_setup_url,omitempty"` } func (x *CreateGroupRequest) Reset() { @@ -143,6 +146,13 @@ func (x *CreateGroupRequest) GetMembers() map[string]Role { return nil } +func (x *CreateGroupRequest) GetSiteSetupUrl() string { + if x != nil { + return x.SiteSetupUrl + } + return "" +} + // Request to get a group. type GetGroupRequest struct { state protoimpl.MessageState @@ -233,6 +243,9 @@ type UpdateGroupRequest struct { // To unpublish content set the value to an empty string for a given pretty path. // Only updated records have to be sent, not all the content of the group. UpdatedContent map[string]string `protobuf:"bytes,5,rep,name=updated_content,json=updatedContent,proto3" json:"updated_content,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Optional. Secret setup URL that is provided during site server deployment. + // I.e. a place on the Web where this group has to be published. + SiteSetupUrl string `protobuf:"bytes,6,opt,name=site_setup_url,json=siteSetupUrl,proto3" json:"site_setup_url,omitempty"` } func (x *UpdateGroupRequest) Reset() { @@ -302,6 +315,13 @@ func (x *UpdateGroupRequest) GetUpdatedContent() map[string]string { return nil } +func (x *UpdateGroupRequest) GetSiteSetupUrl() string { + if x != nil { + return x.SiteSetupUrl + } + return "" +} + // Request to list members. type ListMembersRequest struct { state protoimpl.MessageState @@ -700,267 +720,6 @@ func (x *ListGroupsResponse) GetNextPageToken() string { return "" } -// Makes a site out of an existing group. -type ConvertToSiteRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Required. Secret link provided by the site deployment script. - Link string `protobuf:"bytes,1,opt,name=link,proto3" json:"link,omitempty"` - // Required. Group ID to convert to a site - GroupId string `protobuf:"bytes,2,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` - // Optional. Version of the group to be converted. Latest - // version if not provided - Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` -} - -func (x *ConvertToSiteRequest) Reset() { - *x = ConvertToSiteRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_groups_v1alpha_groups_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ConvertToSiteRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ConvertToSiteRequest) ProtoMessage() {} - -func (x *ConvertToSiteRequest) ProtoReflect() protoreflect.Message { - mi := &file_groups_v1alpha_groups_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ConvertToSiteRequest.ProtoReflect.Descriptor instead. -func (*ConvertToSiteRequest) Descriptor() ([]byte, []int) { - return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{9} -} - -func (x *ConvertToSiteRequest) GetLink() string { - if x != nil { - return x.Link - } - return "" -} - -func (x *ConvertToSiteRequest) GetGroupId() string { - if x != nil { - return x.GroupId - } - return "" -} - -func (x *ConvertToSiteRequest) GetVersion() string { - if x != nil { - return x.Version - } - return "" -} - -// Response to convert to site. -type ConvertToSiteResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Account of the sites owner. - OwnerId string `protobuf:"bytes,1,opt,name=owner_id,json=ownerId,proto3" json:"owner_id,omitempty"` - // Hostname of the site. - Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"` -} - -func (x *ConvertToSiteResponse) Reset() { - *x = ConvertToSiteResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_groups_v1alpha_groups_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ConvertToSiteResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ConvertToSiteResponse) ProtoMessage() {} - -func (x *ConvertToSiteResponse) ProtoReflect() protoreflect.Message { - mi := &file_groups_v1alpha_groups_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ConvertToSiteResponse.ProtoReflect.Descriptor instead. -func (*ConvertToSiteResponse) Descriptor() ([]byte, []int) { - return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{10} -} - -func (x *ConvertToSiteResponse) GetOwnerId() string { - if x != nil { - return x.OwnerId - } - return "" -} - -func (x *ConvertToSiteResponse) GetHostname() string { - if x != nil { - return x.Hostname - } - return "" -} - -// Request to get site info. -type GetSiteInfoRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Required. hostname where the site is published. - Hostname string `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"` - // Optional. Maximum number of members to return. - PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` - // Optional. Page token to continue listing members from. - PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` -} - -func (x *GetSiteInfoRequest) Reset() { - *x = GetSiteInfoRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_groups_v1alpha_groups_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetSiteInfoRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetSiteInfoRequest) ProtoMessage() {} - -func (x *GetSiteInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_groups_v1alpha_groups_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetSiteInfoRequest.ProtoReflect.Descriptor instead. -func (*GetSiteInfoRequest) Descriptor() ([]byte, []int) { - return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{11} -} - -func (x *GetSiteInfoRequest) GetHostname() string { - if x != nil { - return x.Hostname - } - return "" -} - -func (x *GetSiteInfoRequest) GetPageSize() int32 { - if x != nil { - return x.PageSize - } - return 0 -} - -func (x *GetSiteInfoRequest) GetPageToken() string { - if x != nil { - return x.PageToken - } - return "" -} - -// Response to get site info. -type GetSiteInfoResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // ID of the group the site is serving. - GroupId string `protobuf:"bytes,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` - // Current Version of the group that the site is serving. - // Empty means last version. - Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` - // Owner's account of the site. - OwnerId string `protobuf:"bytes,3,opt,name=owner_id,json=ownerId,proto3" json:"owner_id,omitempty"` -} - -func (x *GetSiteInfoResponse) Reset() { - *x = GetSiteInfoResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_groups_v1alpha_groups_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GetSiteInfoResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetSiteInfoResponse) ProtoMessage() {} - -func (x *GetSiteInfoResponse) ProtoReflect() protoreflect.Message { - mi := &file_groups_v1alpha_groups_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetSiteInfoResponse.ProtoReflect.Descriptor instead. -func (*GetSiteInfoResponse) Descriptor() ([]byte, []int) { - return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{12} -} - -func (x *GetSiteInfoResponse) GetGroupId() string { - if x != nil { - return x.GroupId - } - return "" -} - -func (x *GetSiteInfoResponse) GetVersion() string { - if x != nil { - return x.Version - } - return "" -} - -func (x *GetSiteInfoResponse) GetOwnerId() string { - if x != nil { - return x.OwnerId - } - return "" -} - // Request to list groups for a document. type ListDocumentGroupsRequest struct { state protoimpl.MessageState @@ -979,7 +738,7 @@ type ListDocumentGroupsRequest struct { func (x *ListDocumentGroupsRequest) Reset() { *x = ListDocumentGroupsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_groups_v1alpha_groups_proto_msgTypes[13] + mi := &file_groups_v1alpha_groups_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -992,7 +751,7 @@ func (x *ListDocumentGroupsRequest) String() string { func (*ListDocumentGroupsRequest) ProtoMessage() {} func (x *ListDocumentGroupsRequest) ProtoReflect() protoreflect.Message { - mi := &file_groups_v1alpha_groups_proto_msgTypes[13] + mi := &file_groups_v1alpha_groups_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1005,7 +764,7 @@ func (x *ListDocumentGroupsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDocumentGroupsRequest.ProtoReflect.Descriptor instead. func (*ListDocumentGroupsRequest) Descriptor() ([]byte, []int) { - return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{13} + return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{9} } func (x *ListDocumentGroupsRequest) GetDocumentId() string { @@ -1044,7 +803,7 @@ type ListDocumentGroupsResponse struct { func (x *ListDocumentGroupsResponse) Reset() { *x = ListDocumentGroupsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_groups_v1alpha_groups_proto_msgTypes[14] + mi := &file_groups_v1alpha_groups_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1057,7 +816,7 @@ func (x *ListDocumentGroupsResponse) String() string { func (*ListDocumentGroupsResponse) ProtoMessage() {} func (x *ListDocumentGroupsResponse) ProtoReflect() protoreflect.Message { - mi := &file_groups_v1alpha_groups_proto_msgTypes[14] + mi := &file_groups_v1alpha_groups_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1070,7 +829,7 @@ func (x *ListDocumentGroupsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDocumentGroupsResponse.ProtoReflect.Descriptor instead. func (*ListDocumentGroupsResponse) Descriptor() ([]byte, []int) { - return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{14} + return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{10} } func (x *ListDocumentGroupsResponse) GetItems() []*ListDocumentGroupsResponse_Item { @@ -1105,7 +864,7 @@ type ListAccountGroupsRequest struct { func (x *ListAccountGroupsRequest) Reset() { *x = ListAccountGroupsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_groups_v1alpha_groups_proto_msgTypes[15] + mi := &file_groups_v1alpha_groups_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1118,7 +877,7 @@ func (x *ListAccountGroupsRequest) String() string { func (*ListAccountGroupsRequest) ProtoMessage() {} func (x *ListAccountGroupsRequest) ProtoReflect() protoreflect.Message { - mi := &file_groups_v1alpha_groups_proto_msgTypes[15] + mi := &file_groups_v1alpha_groups_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1131,7 +890,7 @@ func (x *ListAccountGroupsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAccountGroupsRequest.ProtoReflect.Descriptor instead. func (*ListAccountGroupsRequest) Descriptor() ([]byte, []int) { - return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{15} + return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{11} } func (x *ListAccountGroupsRequest) GetAccountId() string { @@ -1170,7 +929,7 @@ type ListAccountGroupsResponse struct { func (x *ListAccountGroupsResponse) Reset() { *x = ListAccountGroupsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_groups_v1alpha_groups_proto_msgTypes[16] + mi := &file_groups_v1alpha_groups_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1183,7 +942,7 @@ func (x *ListAccountGroupsResponse) String() string { func (*ListAccountGroupsResponse) ProtoMessage() {} func (x *ListAccountGroupsResponse) ProtoReflect() protoreflect.Message { - mi := &file_groups_v1alpha_groups_proto_msgTypes[16] + mi := &file_groups_v1alpha_groups_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1196,7 +955,7 @@ func (x *ListAccountGroupsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAccountGroupsResponse.ProtoReflect.Descriptor instead. func (*ListAccountGroupsResponse) Descriptor() ([]byte, []int) { - return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{16} + return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{12} } func (x *ListAccountGroupsResponse) GetItems() []*ListAccountGroupsResponse_Item { @@ -1234,12 +993,14 @@ type Group struct { Version string `protobuf:"bytes,6,opt,name=version,proto3" json:"version,omitempty"` // Timestamp of the version of the group. UpdateTime *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty"` + // Optional. Site-related information for groups that are published to sites. + SiteInfo *Group_SiteInfo `protobuf:"bytes,8,opt,name=site_info,json=siteInfo,proto3" json:"site_info,omitempty"` } func (x *Group) Reset() { *x = Group{} if protoimpl.UnsafeEnabled { - mi := &file_groups_v1alpha_groups_proto_msgTypes[17] + mi := &file_groups_v1alpha_groups_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1252,7 +1013,7 @@ func (x *Group) String() string { func (*Group) ProtoMessage() {} func (x *Group) ProtoReflect() protoreflect.Message { - mi := &file_groups_v1alpha_groups_proto_msgTypes[17] + mi := &file_groups_v1alpha_groups_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1265,7 +1026,7 @@ func (x *Group) ProtoReflect() protoreflect.Message { // Deprecated: Use Group.ProtoReflect.Descriptor instead. func (*Group) Descriptor() ([]byte, []int) { - return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{17} + return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{13} } func (x *Group) GetId() string { @@ -1317,6 +1078,13 @@ func (x *Group) GetUpdateTime() *timestamppb.Timestamp { return nil } +func (x *Group) GetSiteInfo() *Group_SiteInfo { + if x != nil { + return x.SiteInfo + } + return nil +} + type ListDocumentGroupsResponse_Item struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1338,7 +1106,7 @@ type ListDocumentGroupsResponse_Item struct { func (x *ListDocumentGroupsResponse_Item) Reset() { *x = ListDocumentGroupsResponse_Item{} if protoimpl.UnsafeEnabled { - mi := &file_groups_v1alpha_groups_proto_msgTypes[23] + mi := &file_groups_v1alpha_groups_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1351,7 +1119,7 @@ func (x *ListDocumentGroupsResponse_Item) String() string { func (*ListDocumentGroupsResponse_Item) ProtoMessage() {} func (x *ListDocumentGroupsResponse_Item) ProtoReflect() protoreflect.Message { - mi := &file_groups_v1alpha_groups_proto_msgTypes[23] + mi := &file_groups_v1alpha_groups_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1364,7 +1132,7 @@ func (x *ListDocumentGroupsResponse_Item) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDocumentGroupsResponse_Item.ProtoReflect.Descriptor instead. func (*ListDocumentGroupsResponse_Item) Descriptor() ([]byte, []int) { - return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{14, 0} + return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{10, 0} } func (x *ListDocumentGroupsResponse_Item) GetGroupId() string { @@ -1414,7 +1182,7 @@ type ListAccountGroupsResponse_Item struct { func (x *ListAccountGroupsResponse_Item) Reset() { *x = ListAccountGroupsResponse_Item{} if protoimpl.UnsafeEnabled { - mi := &file_groups_v1alpha_groups_proto_msgTypes[24] + mi := &file_groups_v1alpha_groups_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1427,7 +1195,7 @@ func (x *ListAccountGroupsResponse_Item) String() string { func (*ListAccountGroupsResponse_Item) ProtoMessage() {} func (x *ListAccountGroupsResponse_Item) ProtoReflect() protoreflect.Message { - mi := &file_groups_v1alpha_groups_proto_msgTypes[24] + mi := &file_groups_v1alpha_groups_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1440,7 +1208,7 @@ func (x *ListAccountGroupsResponse_Item) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAccountGroupsResponse_Item.ProtoReflect.Descriptor instead. func (*ListAccountGroupsResponse_Item) Descriptor() ([]byte, []int) { - return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{16, 0} + return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{12, 0} } func (x *ListAccountGroupsResponse_Item) GetGroup() *Group { @@ -1457,6 +1225,83 @@ func (x *ListAccountGroupsResponse_Item) GetRole() Role { return Role_ROLE_UNSPECIFIED } +// Extra metadata about Site Groups. +type Group_SiteInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Base Web URL of the Site where the Group is being published to. + BaseUrl string `protobuf:"bytes,1,opt,name=base_url,json=baseUrl,proto3" json:"base_url,omitempty"` + // Timestamp of the last sync of the Group's content with the Site. + // This field will be updated regardless of whether we were able to sync or not. + LastSyncTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=last_sync_time,json=lastSyncTime,proto3" json:"last_sync_time,omitempty"` + // Timestamp of the last successful sync of the Group's content with the Site. + LastOkSyncTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=last_ok_sync_time,json=lastOkSyncTime,proto3" json:"last_ok_sync_time,omitempty"` + // Version of the Group as per the Site. + Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"` +} + +func (x *Group_SiteInfo) Reset() { + *x = Group_SiteInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_groups_v1alpha_groups_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Group_SiteInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Group_SiteInfo) ProtoMessage() {} + +func (x *Group_SiteInfo) ProtoReflect() protoreflect.Message { + mi := &file_groups_v1alpha_groups_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Group_SiteInfo.ProtoReflect.Descriptor instead. +func (*Group_SiteInfo) Descriptor() ([]byte, []int) { + return file_groups_v1alpha_groups_proto_rawDescGZIP(), []int{13, 0} +} + +func (x *Group_SiteInfo) GetBaseUrl() string { + if x != nil { + return x.BaseUrl + } + return "" +} + +func (x *Group_SiteInfo) GetLastSyncTime() *timestamppb.Timestamp { + if x != nil { + return x.LastSyncTime + } + return nil +} + +func (x *Group_SiteInfo) GetLastOkSyncTime() *timestamppb.Timestamp { + if x != nil { + return x.LastOkSyncTime + } + return nil +} + +func (x *Group_SiteInfo) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + var File_groups_v1alpha_groups_proto protoreflect.FileDescriptor var file_groups_v1alpha_groups_proto_rawDesc = []byte{ @@ -1465,7 +1310,7 @@ var file_groups_v1alpha_groups_proto_rawDesc = []byte{ 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x81, 0x02, 0x0a, 0x12, 0x43, + 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa7, 0x02, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, @@ -1476,277 +1321,260 @@ var file_groups_v1alpha_groups_proto_rawDesc = []byte{ 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, - 0x1a, 0x5c, 0x0a, 0x0c, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x36, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, - 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, - 0x6f, 0x6c, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x3b, - 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xde, 0x03, 0x0a, 0x12, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x6b, 0x0a, 0x0f, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, - 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x6b, 0x0a, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, - 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x1a, 0x63, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x4d, - 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x36, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x63, - 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, - 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x41, 0x0a, 0x13, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x7a, 0x0a, 0x12, - 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, - 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, - 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x9d, 0x02, 0x0a, 0x13, 0x4c, 0x69, 0x73, - 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x77, 0x6e, 0x65, - 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x56, 0x0a, 0x07, 0x6d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x63, 0x6f, - 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6d, - 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x6d, - 0x62, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, - 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0x5c, 0x0a, 0x0c, 0x4d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x63, 0x6f, - 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x05, 0x76, + 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x75, 0x70, 0x5f, 0x75, + 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x69, 0x74, 0x65, 0x53, 0x65, + 0x74, 0x75, 0x70, 0x55, 0x72, 0x6c, 0x1a, 0x5c, 0x0a, 0x0c, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, + 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0x3b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x22, 0x84, 0x04, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, + 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x6b, 0x0a, 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x62, + 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, + 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x6b, 0x0a, + 0x0f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, + 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x69, + 0x74, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x75, 0x70, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x73, 0x69, 0x74, 0x65, 0x53, 0x65, 0x74, 0x75, 0x70, 0x55, 0x72, 0x6c, + 0x1a, 0x63, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x6d, 0x62, 0x65, + 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, + 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x41, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x7a, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, - 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, + 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xd1, 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x07, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3c, 0x2e, - 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, - 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x63, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, - 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0x3a, 0x0a, 0x0c, - 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4f, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, - 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, - 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x77, 0x0a, 0x12, 0x4c, 0x69, 0x73, - 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x39, 0x0a, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x21, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x72, 0x6f, - 0x75, 0x70, 0x52, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, + 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x9d, 0x02, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x10, + 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x56, 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, + 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x26, + 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, + 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0x5c, 0x0a, 0x0c, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, + 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0x7a, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, + 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x22, 0xd1, 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, + 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, + 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0x3a, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0x4f, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, + 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, + 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x77, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x63, 0x6f, + 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x06, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x78, + 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x47, 0x72, + 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, + 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, + 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, + 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, + 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xc2, 0x02, 0x0a, 0x1a, 0x4c, 0x69, 0x73, + 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, + 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x49, + 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x22, 0x5f, 0x0a, 0x14, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x54, 0x6f, 0x53, - 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, - 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x19, - 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x22, 0x4e, 0x0a, 0x15, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x54, 0x6f, - 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, - 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, - 0x61, 0x6d, 0x65, 0x22, 0x6c, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, - 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, - 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, - 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, - 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x22, 0x65, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, - 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, - 0x70, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, - 0x08, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x22, 0x78, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, - 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x6f, 0x63, 0x75, - 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, - 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, - 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x22, 0xc2, 0x02, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, - 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x51, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x3b, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, - 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, + 0x65, 0x6e, 0x1a, 0xa8, 0x01, 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x54, 0x69, 0x6d, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x70, 0x61, 0x74, 0x68, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x61, 0x77, 0x5f, 0x75, 0x72, 0x6c, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x61, 0x77, 0x55, 0x72, 0x6c, 0x22, 0x75, 0x0a, + 0x18, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, + 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x8c, 0x02, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x50, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x3a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, - 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0xa8, 0x01, 0x0a, - 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, - 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x49, 0x64, 0x12, 0x3b, 0x0a, - 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, - 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x17, - 0x0a, 0x07, 0x72, 0x61, 0x77, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x72, 0x61, 0x77, 0x55, 0x72, 0x6c, 0x22, 0x75, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, - 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x8c, - 0x02, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x47, 0x72, - 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x05, - 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x63, 0x6f, + 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0x75, 0x0a, 0x04, + 0x49, 0x74, 0x65, 0x6d, 0x12, 0x37, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, + 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x34, 0x0a, + 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x26, - 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, - 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x1a, 0x75, 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x37, - 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, - 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, - 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, - 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x34, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x72, + 0x6f, 0x6c, 0x65, 0x22, 0xa1, 0x04, 0x0a, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, + 0x3b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, + 0x69, 0x6d, 0x65, 0x12, 0x47, 0x0a, 0x09, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, + 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2e, 0x53, 0x69, 0x74, 0x65, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x08, 0x73, 0x69, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x1a, 0xc8, 0x01, 0x0a, + 0x08, 0x53, 0x69, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, + 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x73, + 0x65, 0x55, 0x72, 0x6c, 0x12, 0x40, 0x0a, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x79, 0x6e, + 0x63, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x79, + 0x6e, 0x63, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x45, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6f, + 0x6b, 0x5f, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x6c, + 0x61, 0x73, 0x74, 0x4f, 0x6b, 0x53, 0x79, 0x6e, 0x63, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x2a, 0x33, 0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x12, + 0x14, 0x0a, 0x10, 0x52, 0x4f, 0x4c, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x01, + 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x44, 0x49, 0x54, 0x4f, 0x52, 0x10, 0x02, 0x32, 0xfe, 0x06, 0x0a, + 0x06, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x60, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, + 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, + 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x5a, 0x0a, 0x08, 0x47, 0x65, 0x74, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x22, 0x8d, 0x02, - 0x0a, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x20, 0x0a, - 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x28, 0x0a, 0x10, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x77, 0x6e, 0x65, 0x72, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x3b, 0x0a, 0x0b, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x3b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x2a, 0x33, 0x0a, - 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x4f, 0x4c, 0x45, 0x5f, 0x55, 0x4e, - 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x4f, - 0x57, 0x4e, 0x45, 0x52, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x45, 0x44, 0x49, 0x54, 0x4f, 0x52, - 0x10, 0x02, 0x32, 0xe4, 0x08, 0x0a, 0x06, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x60, 0x0a, - 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x2e, 0x2e, 0x63, - 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, - 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, - 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, - 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, - 0x5a, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x2b, 0x2e, 0x63, 0x6f, - 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x72, 0x6f, 0x75, - 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, - 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x60, 0x0a, 0x0b, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, - 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, - 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6f, 0x6d, - 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x6e, 0x0a, - 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x2e, 0x2e, 0x63, - 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, - 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x63, - 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, - 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, - 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6e, 0x0a, - 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x2e, 0x63, - 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, - 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x63, - 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, - 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, - 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x2d, 0x2e, 0x63, 0x6f, - 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x72, 0x6f, - 0x75, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, - 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x72, 0x6f, 0x75, - 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x0d, 0x43, 0x6f, - 0x6e, 0x76, 0x65, 0x72, 0x74, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, 0x30, 0x2e, 0x63, 0x6f, + 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, + 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, + 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x60, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, + 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, + 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x6e, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4d, + 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, + 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, + 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, + 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, + 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, + 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, + 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x83, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x6f, 0x63, + 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x35, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, - 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, - 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, - 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, - 0x72, 0x74, 0x54, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x6e, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, - 0x2e, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, - 0x53, 0x69, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2f, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, - 0x53, 0x69, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x83, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, - 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x35, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, - 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, - 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, - 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x34, 0x2e, 0x63, - 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, - 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x6f, 0x63, + 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x30, 0x5a, 0x2e, 0x6d, 0x69, 0x6e, - 0x74, 0x74, 0x65, 0x72, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x67, 0x65, 0x6e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2f, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x3b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x4c, 0x69, 0x73, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x11, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, + 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, + 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x47, + 0x72, 0x6f, 0x75, 0x70, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x30, 0x5a, + 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, + 0x2f, 0x67, 0x65, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, + 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x3b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1762,7 +1590,7 @@ func file_groups_v1alpha_groups_proto_rawDescGZIP() []byte { } var file_groups_v1alpha_groups_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_groups_v1alpha_groups_proto_msgTypes = make([]protoimpl.MessageInfo, 25) +var file_groups_v1alpha_groups_proto_msgTypes = make([]protoimpl.MessageInfo, 22) var file_groups_v1alpha_groups_proto_goTypes = []interface{}{ (Role)(0), // 0: com.mintter.groups.v1alpha.Role (*CreateGroupRequest)(nil), // 1: com.mintter.groups.v1alpha.CreateGroupRequest @@ -1774,66 +1602,62 @@ var file_groups_v1alpha_groups_proto_goTypes = []interface{}{ (*ListContentResponse)(nil), // 7: com.mintter.groups.v1alpha.ListContentResponse (*ListGroupsRequest)(nil), // 8: com.mintter.groups.v1alpha.ListGroupsRequest (*ListGroupsResponse)(nil), // 9: com.mintter.groups.v1alpha.ListGroupsResponse - (*ConvertToSiteRequest)(nil), // 10: com.mintter.groups.v1alpha.ConvertToSiteRequest - (*ConvertToSiteResponse)(nil), // 11: com.mintter.groups.v1alpha.ConvertToSiteResponse - (*GetSiteInfoRequest)(nil), // 12: com.mintter.groups.v1alpha.GetSiteInfoRequest - (*GetSiteInfoResponse)(nil), // 13: com.mintter.groups.v1alpha.GetSiteInfoResponse - (*ListDocumentGroupsRequest)(nil), // 14: com.mintter.groups.v1alpha.ListDocumentGroupsRequest - (*ListDocumentGroupsResponse)(nil), // 15: com.mintter.groups.v1alpha.ListDocumentGroupsResponse - (*ListAccountGroupsRequest)(nil), // 16: com.mintter.groups.v1alpha.ListAccountGroupsRequest - (*ListAccountGroupsResponse)(nil), // 17: com.mintter.groups.v1alpha.ListAccountGroupsResponse - (*Group)(nil), // 18: com.mintter.groups.v1alpha.Group - nil, // 19: com.mintter.groups.v1alpha.CreateGroupRequest.MembersEntry - nil, // 20: com.mintter.groups.v1alpha.UpdateGroupRequest.UpdatedMembersEntry - nil, // 21: com.mintter.groups.v1alpha.UpdateGroupRequest.UpdatedContentEntry - nil, // 22: com.mintter.groups.v1alpha.ListMembersResponse.MembersEntry - nil, // 23: com.mintter.groups.v1alpha.ListContentResponse.ContentEntry - (*ListDocumentGroupsResponse_Item)(nil), // 24: com.mintter.groups.v1alpha.ListDocumentGroupsResponse.Item - (*ListAccountGroupsResponse_Item)(nil), // 25: com.mintter.groups.v1alpha.ListAccountGroupsResponse.Item - (*timestamppb.Timestamp)(nil), // 26: google.protobuf.Timestamp + (*ListDocumentGroupsRequest)(nil), // 10: com.mintter.groups.v1alpha.ListDocumentGroupsRequest + (*ListDocumentGroupsResponse)(nil), // 11: com.mintter.groups.v1alpha.ListDocumentGroupsResponse + (*ListAccountGroupsRequest)(nil), // 12: com.mintter.groups.v1alpha.ListAccountGroupsRequest + (*ListAccountGroupsResponse)(nil), // 13: com.mintter.groups.v1alpha.ListAccountGroupsResponse + (*Group)(nil), // 14: com.mintter.groups.v1alpha.Group + nil, // 15: com.mintter.groups.v1alpha.CreateGroupRequest.MembersEntry + nil, // 16: com.mintter.groups.v1alpha.UpdateGroupRequest.UpdatedMembersEntry + nil, // 17: com.mintter.groups.v1alpha.UpdateGroupRequest.UpdatedContentEntry + nil, // 18: com.mintter.groups.v1alpha.ListMembersResponse.MembersEntry + nil, // 19: com.mintter.groups.v1alpha.ListContentResponse.ContentEntry + (*ListDocumentGroupsResponse_Item)(nil), // 20: com.mintter.groups.v1alpha.ListDocumentGroupsResponse.Item + (*ListAccountGroupsResponse_Item)(nil), // 21: com.mintter.groups.v1alpha.ListAccountGroupsResponse.Item + (*Group_SiteInfo)(nil), // 22: com.mintter.groups.v1alpha.Group.SiteInfo + (*timestamppb.Timestamp)(nil), // 23: google.protobuf.Timestamp } var file_groups_v1alpha_groups_proto_depIdxs = []int32{ - 19, // 0: com.mintter.groups.v1alpha.CreateGroupRequest.members:type_name -> com.mintter.groups.v1alpha.CreateGroupRequest.MembersEntry - 20, // 1: com.mintter.groups.v1alpha.UpdateGroupRequest.updated_members:type_name -> com.mintter.groups.v1alpha.UpdateGroupRequest.UpdatedMembersEntry - 21, // 2: com.mintter.groups.v1alpha.UpdateGroupRequest.updated_content:type_name -> com.mintter.groups.v1alpha.UpdateGroupRequest.UpdatedContentEntry - 22, // 3: com.mintter.groups.v1alpha.ListMembersResponse.members:type_name -> com.mintter.groups.v1alpha.ListMembersResponse.MembersEntry - 23, // 4: com.mintter.groups.v1alpha.ListContentResponse.content:type_name -> com.mintter.groups.v1alpha.ListContentResponse.ContentEntry - 18, // 5: com.mintter.groups.v1alpha.ListGroupsResponse.groups:type_name -> com.mintter.groups.v1alpha.Group - 24, // 6: com.mintter.groups.v1alpha.ListDocumentGroupsResponse.items:type_name -> com.mintter.groups.v1alpha.ListDocumentGroupsResponse.Item - 25, // 7: com.mintter.groups.v1alpha.ListAccountGroupsResponse.items:type_name -> com.mintter.groups.v1alpha.ListAccountGroupsResponse.Item - 26, // 8: com.mintter.groups.v1alpha.Group.create_time:type_name -> google.protobuf.Timestamp - 26, // 9: com.mintter.groups.v1alpha.Group.update_time:type_name -> google.protobuf.Timestamp - 0, // 10: com.mintter.groups.v1alpha.CreateGroupRequest.MembersEntry.value:type_name -> com.mintter.groups.v1alpha.Role - 0, // 11: com.mintter.groups.v1alpha.UpdateGroupRequest.UpdatedMembersEntry.value:type_name -> com.mintter.groups.v1alpha.Role - 0, // 12: com.mintter.groups.v1alpha.ListMembersResponse.MembersEntry.value:type_name -> com.mintter.groups.v1alpha.Role - 26, // 13: com.mintter.groups.v1alpha.ListDocumentGroupsResponse.Item.change_time:type_name -> google.protobuf.Timestamp - 18, // 14: com.mintter.groups.v1alpha.ListAccountGroupsResponse.Item.group:type_name -> com.mintter.groups.v1alpha.Group - 0, // 15: com.mintter.groups.v1alpha.ListAccountGroupsResponse.Item.role:type_name -> com.mintter.groups.v1alpha.Role - 1, // 16: com.mintter.groups.v1alpha.Groups.CreateGroup:input_type -> com.mintter.groups.v1alpha.CreateGroupRequest - 2, // 17: com.mintter.groups.v1alpha.Groups.GetGroup:input_type -> com.mintter.groups.v1alpha.GetGroupRequest - 3, // 18: com.mintter.groups.v1alpha.Groups.UpdateGroup:input_type -> com.mintter.groups.v1alpha.UpdateGroupRequest - 4, // 19: com.mintter.groups.v1alpha.Groups.ListMembers:input_type -> com.mintter.groups.v1alpha.ListMembersRequest - 6, // 20: com.mintter.groups.v1alpha.Groups.ListContent:input_type -> com.mintter.groups.v1alpha.ListContentRequest - 8, // 21: com.mintter.groups.v1alpha.Groups.ListGroups:input_type -> com.mintter.groups.v1alpha.ListGroupsRequest - 10, // 22: com.mintter.groups.v1alpha.Groups.ConvertToSite:input_type -> com.mintter.groups.v1alpha.ConvertToSiteRequest - 12, // 23: com.mintter.groups.v1alpha.Groups.GetSiteInfo:input_type -> com.mintter.groups.v1alpha.GetSiteInfoRequest - 14, // 24: com.mintter.groups.v1alpha.Groups.ListDocumentGroups:input_type -> com.mintter.groups.v1alpha.ListDocumentGroupsRequest - 16, // 25: com.mintter.groups.v1alpha.Groups.ListAccountGroups:input_type -> com.mintter.groups.v1alpha.ListAccountGroupsRequest - 18, // 26: com.mintter.groups.v1alpha.Groups.CreateGroup:output_type -> com.mintter.groups.v1alpha.Group - 18, // 27: com.mintter.groups.v1alpha.Groups.GetGroup:output_type -> com.mintter.groups.v1alpha.Group - 18, // 28: com.mintter.groups.v1alpha.Groups.UpdateGroup:output_type -> com.mintter.groups.v1alpha.Group - 5, // 29: com.mintter.groups.v1alpha.Groups.ListMembers:output_type -> com.mintter.groups.v1alpha.ListMembersResponse - 7, // 30: com.mintter.groups.v1alpha.Groups.ListContent:output_type -> com.mintter.groups.v1alpha.ListContentResponse - 9, // 31: com.mintter.groups.v1alpha.Groups.ListGroups:output_type -> com.mintter.groups.v1alpha.ListGroupsResponse - 11, // 32: com.mintter.groups.v1alpha.Groups.ConvertToSite:output_type -> com.mintter.groups.v1alpha.ConvertToSiteResponse - 13, // 33: com.mintter.groups.v1alpha.Groups.GetSiteInfo:output_type -> com.mintter.groups.v1alpha.GetSiteInfoResponse - 15, // 34: com.mintter.groups.v1alpha.Groups.ListDocumentGroups:output_type -> com.mintter.groups.v1alpha.ListDocumentGroupsResponse - 17, // 35: com.mintter.groups.v1alpha.Groups.ListAccountGroups:output_type -> com.mintter.groups.v1alpha.ListAccountGroupsResponse - 26, // [26:36] is the sub-list for method output_type - 16, // [16:26] is the sub-list for method input_type - 16, // [16:16] is the sub-list for extension type_name - 16, // [16:16] is the sub-list for extension extendee - 0, // [0:16] is the sub-list for field type_name + 15, // 0: com.mintter.groups.v1alpha.CreateGroupRequest.members:type_name -> com.mintter.groups.v1alpha.CreateGroupRequest.MembersEntry + 16, // 1: com.mintter.groups.v1alpha.UpdateGroupRequest.updated_members:type_name -> com.mintter.groups.v1alpha.UpdateGroupRequest.UpdatedMembersEntry + 17, // 2: com.mintter.groups.v1alpha.UpdateGroupRequest.updated_content:type_name -> com.mintter.groups.v1alpha.UpdateGroupRequest.UpdatedContentEntry + 18, // 3: com.mintter.groups.v1alpha.ListMembersResponse.members:type_name -> com.mintter.groups.v1alpha.ListMembersResponse.MembersEntry + 19, // 4: com.mintter.groups.v1alpha.ListContentResponse.content:type_name -> com.mintter.groups.v1alpha.ListContentResponse.ContentEntry + 14, // 5: com.mintter.groups.v1alpha.ListGroupsResponse.groups:type_name -> com.mintter.groups.v1alpha.Group + 20, // 6: com.mintter.groups.v1alpha.ListDocumentGroupsResponse.items:type_name -> com.mintter.groups.v1alpha.ListDocumentGroupsResponse.Item + 21, // 7: com.mintter.groups.v1alpha.ListAccountGroupsResponse.items:type_name -> com.mintter.groups.v1alpha.ListAccountGroupsResponse.Item + 23, // 8: com.mintter.groups.v1alpha.Group.create_time:type_name -> google.protobuf.Timestamp + 23, // 9: com.mintter.groups.v1alpha.Group.update_time:type_name -> google.protobuf.Timestamp + 22, // 10: com.mintter.groups.v1alpha.Group.site_info:type_name -> com.mintter.groups.v1alpha.Group.SiteInfo + 0, // 11: com.mintter.groups.v1alpha.CreateGroupRequest.MembersEntry.value:type_name -> com.mintter.groups.v1alpha.Role + 0, // 12: com.mintter.groups.v1alpha.UpdateGroupRequest.UpdatedMembersEntry.value:type_name -> com.mintter.groups.v1alpha.Role + 0, // 13: com.mintter.groups.v1alpha.ListMembersResponse.MembersEntry.value:type_name -> com.mintter.groups.v1alpha.Role + 23, // 14: com.mintter.groups.v1alpha.ListDocumentGroupsResponse.Item.change_time:type_name -> google.protobuf.Timestamp + 14, // 15: com.mintter.groups.v1alpha.ListAccountGroupsResponse.Item.group:type_name -> com.mintter.groups.v1alpha.Group + 0, // 16: com.mintter.groups.v1alpha.ListAccountGroupsResponse.Item.role:type_name -> com.mintter.groups.v1alpha.Role + 23, // 17: com.mintter.groups.v1alpha.Group.SiteInfo.last_sync_time:type_name -> google.protobuf.Timestamp + 23, // 18: com.mintter.groups.v1alpha.Group.SiteInfo.last_ok_sync_time:type_name -> google.protobuf.Timestamp + 1, // 19: com.mintter.groups.v1alpha.Groups.CreateGroup:input_type -> com.mintter.groups.v1alpha.CreateGroupRequest + 2, // 20: com.mintter.groups.v1alpha.Groups.GetGroup:input_type -> com.mintter.groups.v1alpha.GetGroupRequest + 3, // 21: com.mintter.groups.v1alpha.Groups.UpdateGroup:input_type -> com.mintter.groups.v1alpha.UpdateGroupRequest + 4, // 22: com.mintter.groups.v1alpha.Groups.ListMembers:input_type -> com.mintter.groups.v1alpha.ListMembersRequest + 6, // 23: com.mintter.groups.v1alpha.Groups.ListContent:input_type -> com.mintter.groups.v1alpha.ListContentRequest + 8, // 24: com.mintter.groups.v1alpha.Groups.ListGroups:input_type -> com.mintter.groups.v1alpha.ListGroupsRequest + 10, // 25: com.mintter.groups.v1alpha.Groups.ListDocumentGroups:input_type -> com.mintter.groups.v1alpha.ListDocumentGroupsRequest + 12, // 26: com.mintter.groups.v1alpha.Groups.ListAccountGroups:input_type -> com.mintter.groups.v1alpha.ListAccountGroupsRequest + 14, // 27: com.mintter.groups.v1alpha.Groups.CreateGroup:output_type -> com.mintter.groups.v1alpha.Group + 14, // 28: com.mintter.groups.v1alpha.Groups.GetGroup:output_type -> com.mintter.groups.v1alpha.Group + 14, // 29: com.mintter.groups.v1alpha.Groups.UpdateGroup:output_type -> com.mintter.groups.v1alpha.Group + 5, // 30: com.mintter.groups.v1alpha.Groups.ListMembers:output_type -> com.mintter.groups.v1alpha.ListMembersResponse + 7, // 31: com.mintter.groups.v1alpha.Groups.ListContent:output_type -> com.mintter.groups.v1alpha.ListContentResponse + 9, // 32: com.mintter.groups.v1alpha.Groups.ListGroups:output_type -> com.mintter.groups.v1alpha.ListGroupsResponse + 11, // 33: com.mintter.groups.v1alpha.Groups.ListDocumentGroups:output_type -> com.mintter.groups.v1alpha.ListDocumentGroupsResponse + 13, // 34: com.mintter.groups.v1alpha.Groups.ListAccountGroups:output_type -> com.mintter.groups.v1alpha.ListAccountGroupsResponse + 27, // [27:35] is the sub-list for method output_type + 19, // [19:27] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name } func init() { file_groups_v1alpha_groups_proto_init() } @@ -1951,7 +1775,7 @@ func file_groups_v1alpha_groups_proto_init() { } } file_groups_v1alpha_groups_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ConvertToSiteRequest); i { + switch v := v.(*ListDocumentGroupsRequest); i { case 0: return &v.state case 1: @@ -1963,7 +1787,7 @@ func file_groups_v1alpha_groups_proto_init() { } } file_groups_v1alpha_groups_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ConvertToSiteResponse); i { + switch v := v.(*ListDocumentGroupsResponse); i { case 0: return &v.state case 1: @@ -1975,7 +1799,7 @@ func file_groups_v1alpha_groups_proto_init() { } } file_groups_v1alpha_groups_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetSiteInfoRequest); i { + switch v := v.(*ListAccountGroupsRequest); i { case 0: return &v.state case 1: @@ -1987,7 +1811,7 @@ func file_groups_v1alpha_groups_proto_init() { } } file_groups_v1alpha_groups_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetSiteInfoResponse); i { + switch v := v.(*ListAccountGroupsResponse); i { case 0: return &v.state case 1: @@ -1999,43 +1823,7 @@ func file_groups_v1alpha_groups_proto_init() { } } file_groups_v1alpha_groups_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListDocumentGroupsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_groups_v1alpha_groups_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListDocumentGroupsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_groups_v1alpha_groups_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAccountGroupsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_groups_v1alpha_groups_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAccountGroupsResponse); i { + switch v := v.(*Group); i { case 0: return &v.state case 1: @@ -2046,8 +1834,8 @@ func file_groups_v1alpha_groups_proto_init() { return nil } } - file_groups_v1alpha_groups_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Group); i { + file_groups_v1alpha_groups_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListDocumentGroupsResponse_Item); i { case 0: return &v.state case 1: @@ -2058,8 +1846,8 @@ func file_groups_v1alpha_groups_proto_init() { return nil } } - file_groups_v1alpha_groups_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListDocumentGroupsResponse_Item); i { + file_groups_v1alpha_groups_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListAccountGroupsResponse_Item); i { case 0: return &v.state case 1: @@ -2070,8 +1858,8 @@ func file_groups_v1alpha_groups_proto_init() { return nil } } - file_groups_v1alpha_groups_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAccountGroupsResponse_Item); i { + file_groups_v1alpha_groups_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Group_SiteInfo); i { case 0: return &v.state case 1: @@ -2089,7 +1877,7 @@ func file_groups_v1alpha_groups_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_groups_v1alpha_groups_proto_rawDesc, NumEnums: 1, - NumMessages: 25, + NumMessages: 22, NumExtensions: 0, NumServices: 1, }, diff --git a/backend/genproto/groups/v1alpha/groups_grpc.pb.go b/backend/genproto/groups/v1alpha/groups_grpc.pb.go index 4823ff164c..9e9d140ff4 100644 --- a/backend/genproto/groups/v1alpha/groups_grpc.pb.go +++ b/backend/genproto/groups/v1alpha/groups_grpc.pb.go @@ -34,10 +34,6 @@ type GroupsClient interface { ListContent(ctx context.Context, in *ListContentRequest, opts ...grpc.CallOption) (*ListContentResponse, error) // Lists groups. ListGroups(ctx context.Context, in *ListGroupsRequest, opts ...grpc.CallOption) (*ListGroupsResponse, error) - // Converts a group to a site. P2P group will continue to work. - ConvertToSite(ctx context.Context, in *ConvertToSiteRequest, opts ...grpc.CallOption) (*ConvertToSiteResponse, error) - // Gets information about a site. - GetSiteInfo(ctx context.Context, in *GetSiteInfoRequest, opts ...grpc.CallOption) (*GetSiteInfoResponse, error) // Lists groups that a document is published to. ListDocumentGroups(ctx context.Context, in *ListDocumentGroupsRequest, opts ...grpc.CallOption) (*ListDocumentGroupsResponse, error) // Lists groups that an account is a member of. @@ -106,24 +102,6 @@ func (c *groupsClient) ListGroups(ctx context.Context, in *ListGroupsRequest, op return out, nil } -func (c *groupsClient) ConvertToSite(ctx context.Context, in *ConvertToSiteRequest, opts ...grpc.CallOption) (*ConvertToSiteResponse, error) { - out := new(ConvertToSiteResponse) - err := c.cc.Invoke(ctx, "/com.mintter.groups.v1alpha.Groups/ConvertToSite", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *groupsClient) GetSiteInfo(ctx context.Context, in *GetSiteInfoRequest, opts ...grpc.CallOption) (*GetSiteInfoResponse, error) { - out := new(GetSiteInfoResponse) - err := c.cc.Invoke(ctx, "/com.mintter.groups.v1alpha.Groups/GetSiteInfo", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *groupsClient) ListDocumentGroups(ctx context.Context, in *ListDocumentGroupsRequest, opts ...grpc.CallOption) (*ListDocumentGroupsResponse, error) { out := new(ListDocumentGroupsResponse) err := c.cc.Invoke(ctx, "/com.mintter.groups.v1alpha.Groups/ListDocumentGroups", in, out, opts...) @@ -158,10 +136,6 @@ type GroupsServer interface { ListContent(context.Context, *ListContentRequest) (*ListContentResponse, error) // Lists groups. ListGroups(context.Context, *ListGroupsRequest) (*ListGroupsResponse, error) - // Converts a group to a site. P2P group will continue to work. - ConvertToSite(context.Context, *ConvertToSiteRequest) (*ConvertToSiteResponse, error) - // Gets information about a site. - GetSiteInfo(context.Context, *GetSiteInfoRequest) (*GetSiteInfoResponse, error) // Lists groups that a document is published to. ListDocumentGroups(context.Context, *ListDocumentGroupsRequest) (*ListDocumentGroupsResponse, error) // Lists groups that an account is a member of. @@ -190,12 +164,6 @@ func (UnimplementedGroupsServer) ListContent(context.Context, *ListContentReques func (UnimplementedGroupsServer) ListGroups(context.Context, *ListGroupsRequest) (*ListGroupsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListGroups not implemented") } -func (UnimplementedGroupsServer) ConvertToSite(context.Context, *ConvertToSiteRequest) (*ConvertToSiteResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ConvertToSite not implemented") -} -func (UnimplementedGroupsServer) GetSiteInfo(context.Context, *GetSiteInfoRequest) (*GetSiteInfoResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetSiteInfo not implemented") -} func (UnimplementedGroupsServer) ListDocumentGroups(context.Context, *ListDocumentGroupsRequest) (*ListDocumentGroupsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListDocumentGroups not implemented") } @@ -322,42 +290,6 @@ func _Groups_ListGroups_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } -func _Groups_ConvertToSite_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ConvertToSiteRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(GroupsServer).ConvertToSite(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.groups.v1alpha.Groups/ConvertToSite", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(GroupsServer).ConvertToSite(ctx, req.(*ConvertToSiteRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Groups_GetSiteInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetSiteInfoRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(GroupsServer).GetSiteInfo(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.groups.v1alpha.Groups/GetSiteInfo", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(GroupsServer).GetSiteInfo(ctx, req.(*GetSiteInfoRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _Groups_ListDocumentGroups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListDocumentGroupsRequest) if err := dec(in); err != nil { @@ -425,14 +357,6 @@ var Groups_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListGroups", Handler: _Groups_ListGroups_Handler, }, - { - MethodName: "ConvertToSite", - Handler: _Groups_ConvertToSite_Handler, - }, - { - MethodName: "GetSiteInfo", - Handler: _Groups_GetSiteInfo_Handler, - }, { MethodName: "ListDocumentGroups", Handler: _Groups_ListDocumentGroups_Handler, diff --git a/backend/genproto/groups/v1alpha/website.pb.go b/backend/genproto/groups/v1alpha/website.pb.go new file mode 100644 index 0000000000..742d531a1a --- /dev/null +++ b/backend/genproto/groups/v1alpha/website.pb.go @@ -0,0 +1,595 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc v3.21.12 +// source: groups/v1alpha/website.proto + +package groups + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Request for getting the public site information. +type GetSiteInfoRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetSiteInfoRequest) Reset() { + *x = GetSiteInfoRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_groups_v1alpha_website_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetSiteInfoRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSiteInfoRequest) ProtoMessage() {} + +func (x *GetSiteInfoRequest) ProtoReflect() protoreflect.Message { + mi := &file_groups_v1alpha_website_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSiteInfoRequest.ProtoReflect.Descriptor instead. +func (*GetSiteInfoRequest) Descriptor() ([]byte, []int) { + return file_groups_v1alpha_website_proto_rawDescGZIP(), []int{0} +} + +// Request for initializing the site. +type InitializeServerRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Required. The secret provided during the site deployment process. + // It's a trust-on-first-use, one-time-use secret that is used for the initial site setup, + // during which the site remembers the groups that it must serve, and who is the owner of the site. + Secret string `protobuf:"bytes,1,opt,name=secret,proto3" json:"secret,omitempty"` + // Required. ID of the group that should be served on this site. + GroupId string `protobuf:"bytes,2,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` +} + +func (x *InitializeServerRequest) Reset() { + *x = InitializeServerRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_groups_v1alpha_website_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InitializeServerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InitializeServerRequest) ProtoMessage() {} + +func (x *InitializeServerRequest) ProtoReflect() protoreflect.Message { + mi := &file_groups_v1alpha_website_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InitializeServerRequest.ProtoReflect.Descriptor instead. +func (*InitializeServerRequest) Descriptor() ([]byte, []int) { + return file_groups_v1alpha_website_proto_rawDescGZIP(), []int{1} +} + +func (x *InitializeServerRequest) GetSecret() string { + if x != nil { + return x.Secret + } + return "" +} + +func (x *InitializeServerRequest) GetGroupId() string { + if x != nil { + return x.GroupId + } + return "" +} + +// Response for initializing the site. +type InitializeServerResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *InitializeServerResponse) Reset() { + *x = InitializeServerResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_groups_v1alpha_website_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InitializeServerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InitializeServerResponse) ProtoMessage() {} + +func (x *InitializeServerResponse) ProtoReflect() protoreflect.Message { + mi := &file_groups_v1alpha_website_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InitializeServerResponse.ProtoReflect.Descriptor instead. +func (*InitializeServerResponse) Descriptor() ([]byte, []int) { + return file_groups_v1alpha_website_proto_rawDescGZIP(), []int{2} +} + +// Request for publishing blobs. +type PublishBlobsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // List of blob CIDs that we expect to be available on the site. + Blobs []string `protobuf:"bytes,1,rep,name=blobs,proto3" json:"blobs,omitempty"` +} + +func (x *PublishBlobsRequest) Reset() { + *x = PublishBlobsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_groups_v1alpha_website_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PublishBlobsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PublishBlobsRequest) ProtoMessage() {} + +func (x *PublishBlobsRequest) ProtoReflect() protoreflect.Message { + mi := &file_groups_v1alpha_website_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PublishBlobsRequest.ProtoReflect.Descriptor instead. +func (*PublishBlobsRequest) Descriptor() ([]byte, []int) { + return file_groups_v1alpha_website_proto_rawDescGZIP(), []int{3} +} + +func (x *PublishBlobsRequest) GetBlobs() []string { + if x != nil { + return x.Blobs + } + return nil +} + +// Response for publishing blobs. +type PublishBlobsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *PublishBlobsResponse) Reset() { + *x = PublishBlobsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_groups_v1alpha_website_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PublishBlobsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PublishBlobsResponse) ProtoMessage() {} + +func (x *PublishBlobsResponse) ProtoReflect() protoreflect.Message { + mi := &file_groups_v1alpha_website_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PublishBlobsResponse.ProtoReflect.Descriptor instead. +func (*PublishBlobsResponse) Descriptor() ([]byte, []int) { + return file_groups_v1alpha_website_proto_rawDescGZIP(), []int{4} +} + +// Publicly available information about the website. +type PublicSiteInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // P2P information for the website. + PeerInfo *PeerInfo `protobuf:"bytes,1,opt,name=peer_info,json=peerInfo,proto3" json:"peer_info,omitempty"` + // Group ID being served on the site. + // Can be empty if site is not initialized yet. + GroupId string `protobuf:"bytes,2,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` + // Version of the group according to the website server. + GroupVersion string `protobuf:"bytes,3,opt,name=group_version,json=groupVersion,proto3" json:"group_version,omitempty"` +} + +func (x *PublicSiteInfo) Reset() { + *x = PublicSiteInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_groups_v1alpha_website_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PublicSiteInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PublicSiteInfo) ProtoMessage() {} + +func (x *PublicSiteInfo) ProtoReflect() protoreflect.Message { + mi := &file_groups_v1alpha_website_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PublicSiteInfo.ProtoReflect.Descriptor instead. +func (*PublicSiteInfo) Descriptor() ([]byte, []int) { + return file_groups_v1alpha_website_proto_rawDescGZIP(), []int{5} +} + +func (x *PublicSiteInfo) GetPeerInfo() *PeerInfo { + if x != nil { + return x.PeerInfo + } + return nil +} + +func (x *PublicSiteInfo) GetGroupId() string { + if x != nil { + return x.GroupId + } + return "" +} + +func (x *PublicSiteInfo) GetGroupVersion() string { + if x != nil { + return x.GroupVersion + } + return "" +} + +// Peer information for P2P network. +type PeerInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Libp2p peer ID. + PeerId string `protobuf:"bytes,1,opt,name=peer_id,json=peerId,proto3" json:"peer_id,omitempty"` + // Multiaddrs for the peer, + // without the peer ID, + // in order to use it with libp2p AddrInfo API. + Addrs []string `protobuf:"bytes,2,rep,name=addrs,proto3" json:"addrs,omitempty"` + // Mintter Account ID of the site. + AccountId string `protobuf:"bytes,3,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` +} + +func (x *PeerInfo) Reset() { + *x = PeerInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_groups_v1alpha_website_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PeerInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PeerInfo) ProtoMessage() {} + +func (x *PeerInfo) ProtoReflect() protoreflect.Message { + mi := &file_groups_v1alpha_website_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PeerInfo.ProtoReflect.Descriptor instead. +func (*PeerInfo) Descriptor() ([]byte, []int) { + return file_groups_v1alpha_website_proto_rawDescGZIP(), []int{6} +} + +func (x *PeerInfo) GetPeerId() string { + if x != nil { + return x.PeerId + } + return "" +} + +func (x *PeerInfo) GetAddrs() []string { + if x != nil { + return x.Addrs + } + return nil +} + +func (x *PeerInfo) GetAccountId() string { + if x != nil { + return x.AccountId + } + return "" +} + +var File_groups_v1alpha_website_proto protoreflect.FileDescriptor + +var file_groups_v1alpha_website_proto_rawDesc = []byte{ + 0x0a, 0x1c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x2f, 0x77, 0x65, 0x62, 0x73, 0x69, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, + 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x22, 0x14, 0x0a, 0x12, 0x47, 0x65, + 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x22, 0x4c, 0x0a, 0x17, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x22, 0x1a, + 0x0a, 0x18, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x0a, 0x13, 0x50, 0x75, + 0x62, 0x6c, 0x69, 0x73, 0x68, 0x42, 0x6c, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x62, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x62, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x50, 0x75, 0x62, 0x6c, 0x69, + 0x73, 0x68, 0x42, 0x6c, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x93, 0x01, 0x0a, 0x0e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x53, 0x69, 0x74, 0x65, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x41, 0x0a, 0x09, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, + 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x70, 0x65, 0x65, + 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, + 0x12, 0x23, 0x0a, 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x58, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x49, 0x6e, 0x66, + 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x64, + 0x64, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x61, 0x64, 0x64, 0x72, 0x73, + 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x32, + 0xe6, 0x02, 0x0a, 0x07, 0x57, 0x65, 0x62, 0x73, 0x69, 0x74, 0x65, 0x12, 0x69, 0x0a, 0x0b, 0x47, + 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, + 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x69, 0x74, 0x65, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x63, 0x6f, 0x6d, + 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x53, 0x69, + 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x7d, 0x0a, 0x10, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x6d, + 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, + 0x7a, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x34, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, + 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x49, 0x6e, 0x69, + 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x0c, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, + 0x42, 0x6c, 0x6f, 0x62, 0x73, 0x12, 0x2f, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, + 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x42, 0x6c, 0x6f, 0x62, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, + 0x74, 0x74, 0x65, 0x72, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x42, 0x6c, 0x6f, 0x62, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x30, 0x5a, 0x2e, 0x6d, 0x69, 0x6e, 0x74, + 0x74, 0x65, 0x72, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x67, 0x65, 0x6e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x2f, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x3b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_groups_v1alpha_website_proto_rawDescOnce sync.Once + file_groups_v1alpha_website_proto_rawDescData = file_groups_v1alpha_website_proto_rawDesc +) + +func file_groups_v1alpha_website_proto_rawDescGZIP() []byte { + file_groups_v1alpha_website_proto_rawDescOnce.Do(func() { + file_groups_v1alpha_website_proto_rawDescData = protoimpl.X.CompressGZIP(file_groups_v1alpha_website_proto_rawDescData) + }) + return file_groups_v1alpha_website_proto_rawDescData +} + +var file_groups_v1alpha_website_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_groups_v1alpha_website_proto_goTypes = []interface{}{ + (*GetSiteInfoRequest)(nil), // 0: com.mintter.groups.v1alpha.GetSiteInfoRequest + (*InitializeServerRequest)(nil), // 1: com.mintter.groups.v1alpha.InitializeServerRequest + (*InitializeServerResponse)(nil), // 2: com.mintter.groups.v1alpha.InitializeServerResponse + (*PublishBlobsRequest)(nil), // 3: com.mintter.groups.v1alpha.PublishBlobsRequest + (*PublishBlobsResponse)(nil), // 4: com.mintter.groups.v1alpha.PublishBlobsResponse + (*PublicSiteInfo)(nil), // 5: com.mintter.groups.v1alpha.PublicSiteInfo + (*PeerInfo)(nil), // 6: com.mintter.groups.v1alpha.PeerInfo +} +var file_groups_v1alpha_website_proto_depIdxs = []int32{ + 6, // 0: com.mintter.groups.v1alpha.PublicSiteInfo.peer_info:type_name -> com.mintter.groups.v1alpha.PeerInfo + 0, // 1: com.mintter.groups.v1alpha.Website.GetSiteInfo:input_type -> com.mintter.groups.v1alpha.GetSiteInfoRequest + 1, // 2: com.mintter.groups.v1alpha.Website.InitializeServer:input_type -> com.mintter.groups.v1alpha.InitializeServerRequest + 3, // 3: com.mintter.groups.v1alpha.Website.PublishBlobs:input_type -> com.mintter.groups.v1alpha.PublishBlobsRequest + 5, // 4: com.mintter.groups.v1alpha.Website.GetSiteInfo:output_type -> com.mintter.groups.v1alpha.PublicSiteInfo + 2, // 5: com.mintter.groups.v1alpha.Website.InitializeServer:output_type -> com.mintter.groups.v1alpha.InitializeServerResponse + 4, // 6: com.mintter.groups.v1alpha.Website.PublishBlobs:output_type -> com.mintter.groups.v1alpha.PublishBlobsResponse + 4, // [4:7] is the sub-list for method output_type + 1, // [1:4] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_groups_v1alpha_website_proto_init() } +func file_groups_v1alpha_website_proto_init() { + if File_groups_v1alpha_website_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_groups_v1alpha_website_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetSiteInfoRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_groups_v1alpha_website_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InitializeServerRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_groups_v1alpha_website_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InitializeServerResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_groups_v1alpha_website_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PublishBlobsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_groups_v1alpha_website_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PublishBlobsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_groups_v1alpha_website_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PublicSiteInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_groups_v1alpha_website_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PeerInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_groups_v1alpha_website_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_groups_v1alpha_website_proto_goTypes, + DependencyIndexes: file_groups_v1alpha_website_proto_depIdxs, + MessageInfos: file_groups_v1alpha_website_proto_msgTypes, + }.Build() + File_groups_v1alpha_website_proto = out.File + file_groups_v1alpha_website_proto_rawDesc = nil + file_groups_v1alpha_website_proto_goTypes = nil + file_groups_v1alpha_website_proto_depIdxs = nil +} diff --git a/backend/genproto/groups/v1alpha/website_grpc.pb.go b/backend/genproto/groups/v1alpha/website_grpc.pb.go new file mode 100644 index 0000000000..6f974c0785 --- /dev/null +++ b/backend/genproto/groups/v1alpha/website_grpc.pb.go @@ -0,0 +1,183 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.21.12 +// source: groups/v1alpha/website.proto + +package groups + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// WebsiteClient is the client API for Website service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type WebsiteClient interface { + // Gets the public information about the website. + // This information is also available as JSON over HTTP on `/.well-known/hypermedia-site`. + GetSiteInfo(ctx context.Context, in *GetSiteInfoRequest, opts ...grpc.CallOption) (*PublicSiteInfo, error) + // Initializes the server to become a website for a specific group. + InitializeServer(ctx context.Context, in *InitializeServerRequest, opts ...grpc.CallOption) (*InitializeServerResponse, error) + // Publishes blobs to the website. + PublishBlobs(ctx context.Context, in *PublishBlobsRequest, opts ...grpc.CallOption) (*PublishBlobsResponse, error) +} + +type websiteClient struct { + cc grpc.ClientConnInterface +} + +func NewWebsiteClient(cc grpc.ClientConnInterface) WebsiteClient { + return &websiteClient{cc} +} + +func (c *websiteClient) GetSiteInfo(ctx context.Context, in *GetSiteInfoRequest, opts ...grpc.CallOption) (*PublicSiteInfo, error) { + out := new(PublicSiteInfo) + err := c.cc.Invoke(ctx, "/com.mintter.groups.v1alpha.Website/GetSiteInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *websiteClient) InitializeServer(ctx context.Context, in *InitializeServerRequest, opts ...grpc.CallOption) (*InitializeServerResponse, error) { + out := new(InitializeServerResponse) + err := c.cc.Invoke(ctx, "/com.mintter.groups.v1alpha.Website/InitializeServer", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *websiteClient) PublishBlobs(ctx context.Context, in *PublishBlobsRequest, opts ...grpc.CallOption) (*PublishBlobsResponse, error) { + out := new(PublishBlobsResponse) + err := c.cc.Invoke(ctx, "/com.mintter.groups.v1alpha.Website/PublishBlobs", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// WebsiteServer is the server API for Website service. +// All implementations should embed UnimplementedWebsiteServer +// for forward compatibility +type WebsiteServer interface { + // Gets the public information about the website. + // This information is also available as JSON over HTTP on `/.well-known/hypermedia-site`. + GetSiteInfo(context.Context, *GetSiteInfoRequest) (*PublicSiteInfo, error) + // Initializes the server to become a website for a specific group. + InitializeServer(context.Context, *InitializeServerRequest) (*InitializeServerResponse, error) + // Publishes blobs to the website. + PublishBlobs(context.Context, *PublishBlobsRequest) (*PublishBlobsResponse, error) +} + +// UnimplementedWebsiteServer should be embedded to have forward compatible implementations. +type UnimplementedWebsiteServer struct { +} + +func (UnimplementedWebsiteServer) GetSiteInfo(context.Context, *GetSiteInfoRequest) (*PublicSiteInfo, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetSiteInfo not implemented") +} +func (UnimplementedWebsiteServer) InitializeServer(context.Context, *InitializeServerRequest) (*InitializeServerResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method InitializeServer not implemented") +} +func (UnimplementedWebsiteServer) PublishBlobs(context.Context, *PublishBlobsRequest) (*PublishBlobsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PublishBlobs not implemented") +} + +// UnsafeWebsiteServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to WebsiteServer will +// result in compilation errors. +type UnsafeWebsiteServer interface { + mustEmbedUnimplementedWebsiteServer() +} + +func RegisterWebsiteServer(s grpc.ServiceRegistrar, srv WebsiteServer) { + s.RegisterService(&Website_ServiceDesc, srv) +} + +func _Website_GetSiteInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetSiteInfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WebsiteServer).GetSiteInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/com.mintter.groups.v1alpha.Website/GetSiteInfo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WebsiteServer).GetSiteInfo(ctx, req.(*GetSiteInfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Website_InitializeServer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(InitializeServerRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WebsiteServer).InitializeServer(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/com.mintter.groups.v1alpha.Website/InitializeServer", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WebsiteServer).InitializeServer(ctx, req.(*InitializeServerRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Website_PublishBlobs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PublishBlobsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WebsiteServer).PublishBlobs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/com.mintter.groups.v1alpha.Website/PublishBlobs", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WebsiteServer).PublishBlobs(ctx, req.(*PublishBlobsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Website_ServiceDesc is the grpc.ServiceDesc for Website service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Website_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "com.mintter.groups.v1alpha.Website", + HandlerType: (*WebsiteServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetSiteInfo", + Handler: _Website_GetSiteInfo_Handler, + }, + { + MethodName: "InitializeServer", + Handler: _Website_InitializeServer_Handler, + }, + { + MethodName: "PublishBlobs", + Handler: _Website_PublishBlobs_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "groups/v1alpha/website.proto", +} diff --git a/backend/genproto/networking/v1alpha/networking.pb.go b/backend/genproto/networking/v1alpha/networking.pb.go index ccd892ed3c..73effa908f 100644 --- a/backend/genproto/networking/v1alpha/networking.pb.go +++ b/backend/genproto/networking/v1alpha/networking.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.21.12 // source: networking/v1alpha/networking.proto diff --git a/backend/genproto/p2p/v1alpha/p2p.pb.go b/backend/genproto/p2p/v1alpha/p2p.pb.go index 53faa82300..668665aa62 100644 --- a/backend/genproto/p2p/v1alpha/p2p.pb.go +++ b/backend/genproto/p2p/v1alpha/p2p.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.30.0 +// protoc-gen-go v1.31.0 // protoc v3.21.12 // source: p2p/v1alpha/p2p.proto @@ -342,132 +342,6 @@ func (x *RequestInvoiceResponse) GetPayReq() string { return "" } -// Links a server and a group. -type CreateSiteRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Secret link provided by the deployment script. - Link string `protobuf:"bytes,1,opt,name=link,proto3" json:"link,omitempty"` - // ID of the group to be served. - GroupId string `protobuf:"bytes,2,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` - // Optional version of the group to be served. - // If empty, latest version is assumed. - Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` -} - -func (x *CreateSiteRequest) Reset() { - *x = CreateSiteRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_p2p_v1alpha_p2p_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CreateSiteRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CreateSiteRequest) ProtoMessage() {} - -func (x *CreateSiteRequest) ProtoReflect() protoreflect.Message { - mi := &file_p2p_v1alpha_p2p_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CreateSiteRequest.ProtoReflect.Descriptor instead. -func (*CreateSiteRequest) Descriptor() ([]byte, []int) { - return file_p2p_v1alpha_p2p_proto_rawDescGZIP(), []int{6} -} - -func (x *CreateSiteRequest) GetLink() string { - if x != nil { - return x.Link - } - return "" -} - -func (x *CreateSiteRequest) GetGroupId() string { - if x != nil { - return x.GroupId - } - return "" -} - -func (x *CreateSiteRequest) GetVersion() string { - if x != nil { - return x.Version - } - return "" -} - -// Create site response -type CreateSiteResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Owner of the site. - OwnerId string `protobuf:"bytes,1,opt,name=owner_id,json=ownerId,proto3" json:"owner_id,omitempty"` - // ID of the site. - GroupId string `protobuf:"bytes,2,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` -} - -func (x *CreateSiteResponse) Reset() { - *x = CreateSiteResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_p2p_v1alpha_p2p_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CreateSiteResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CreateSiteResponse) ProtoMessage() {} - -func (x *CreateSiteResponse) ProtoReflect() protoreflect.Message { - mi := &file_p2p_v1alpha_p2p_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CreateSiteResponse.ProtoReflect.Descriptor instead. -func (*CreateSiteResponse) Descriptor() ([]byte, []int) { - return file_p2p_v1alpha_p2p_proto_rawDescGZIP(), []int{7} -} - -func (x *CreateSiteResponse) GetOwnerId() string { - if x != nil { - return x.OwnerId - } - return "" -} - -func (x *CreateSiteResponse) GetGroupId() string { - if x != nil { - return x.GroupId - } - return "" -} - var File_p2p_v1alpha_p2p_proto protoreflect.FileDescriptor var file_p2p_v1alpha_p2p_proto_rawDesc = []byte{ @@ -503,48 +377,30 @@ var file_p2p_v1alpha_p2p_proto_rawDesc = []byte{ 0x61, 0x67, 0x65, 0x48, 0x61, 0x73, 0x68, 0x22, 0x31, 0x0a, 0x16, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x79, 0x52, 0x65, 0x71, 0x22, 0x5c, 0x0a, 0x11, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, - 0x69, 0x6e, 0x6b, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x18, - 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x4a, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, - 0x0a, 0x08, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x49, 0x64, 0x32, 0xa6, 0x03, 0x0a, 0x03, 0x50, 0x32, 0x50, 0x12, 0x5b, 0x0a, 0x09, - 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, - 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x49, 0x6e, 0x66, - 0x6f, 0x1a, 0x26, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, - 0x70, 0x32, 0x70, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x48, 0x61, 0x6e, 0x64, - 0x73, 0x68, 0x61, 0x6b, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x68, 0x0a, 0x0b, 0x4c, 0x69, 0x73, - 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, - 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, - 0x74, 0x65, 0x72, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x0e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, - 0x74, 0x65, 0x72, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, - 0x74, 0x65, 0x72, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x53, 0x69, 0x74, 0x65, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, - 0x65, 0x72, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x70, - 0x32, 0x70, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x53, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2a, 0x5a, - 0x28, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, - 0x2f, 0x67, 0x65, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x32, 0x70, 0x2f, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x3b, 0x70, 0x32, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x79, 0x52, 0x65, 0x71, 0x32, 0xbf, 0x02, 0x0a, 0x03, 0x50, + 0x32, 0x50, 0x12, 0x5b, 0x0a, 0x09, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, + 0x26, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x32, + 0x70, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, + 0x61, 0x6b, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x1a, 0x26, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, + 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, + 0x68, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x2b, + 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x32, 0x70, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, + 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x0e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x2e, 0x2e, 0x63, 0x6f, + 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x63, 0x6f, + 0x6d, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2a, 0x5a, 0x28, + 0x6d, 0x69, 0x6e, 0x74, 0x74, 0x65, 0x72, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, + 0x67, 0x65, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x32, 0x70, 0x2f, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x3b, 0x70, 0x32, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -559,7 +415,7 @@ func file_p2p_v1alpha_p2p_proto_rawDescGZIP() []byte { return file_p2p_v1alpha_p2p_proto_rawDescData } -var file_p2p_v1alpha_p2p_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_p2p_v1alpha_p2p_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_p2p_v1alpha_p2p_proto_goTypes = []interface{}{ (*HandshakeInfo)(nil), // 0: com.mintter.p2p.v1alpha.HandshakeInfo (*ListObjectsRequest)(nil), // 1: com.mintter.p2p.v1alpha.ListObjectsRequest @@ -567,21 +423,17 @@ var file_p2p_v1alpha_p2p_proto_goTypes = []interface{}{ (*Object)(nil), // 3: com.mintter.p2p.v1alpha.Object (*RequestInvoiceRequest)(nil), // 4: com.mintter.p2p.v1alpha.RequestInvoiceRequest (*RequestInvoiceResponse)(nil), // 5: com.mintter.p2p.v1alpha.RequestInvoiceResponse - (*CreateSiteRequest)(nil), // 6: com.mintter.p2p.v1alpha.CreateSiteRequest - (*CreateSiteResponse)(nil), // 7: com.mintter.p2p.v1alpha.CreateSiteResponse } var file_p2p_v1alpha_p2p_proto_depIdxs = []int32{ 3, // 0: com.mintter.p2p.v1alpha.ListObjectsResponse.objects:type_name -> com.mintter.p2p.v1alpha.Object 0, // 1: com.mintter.p2p.v1alpha.P2P.Handshake:input_type -> com.mintter.p2p.v1alpha.HandshakeInfo 1, // 2: com.mintter.p2p.v1alpha.P2P.ListObjects:input_type -> com.mintter.p2p.v1alpha.ListObjectsRequest 4, // 3: com.mintter.p2p.v1alpha.P2P.RequestInvoice:input_type -> com.mintter.p2p.v1alpha.RequestInvoiceRequest - 6, // 4: com.mintter.p2p.v1alpha.P2P.CreateSite:input_type -> com.mintter.p2p.v1alpha.CreateSiteRequest - 0, // 5: com.mintter.p2p.v1alpha.P2P.Handshake:output_type -> com.mintter.p2p.v1alpha.HandshakeInfo - 2, // 6: com.mintter.p2p.v1alpha.P2P.ListObjects:output_type -> com.mintter.p2p.v1alpha.ListObjectsResponse - 5, // 7: com.mintter.p2p.v1alpha.P2P.RequestInvoice:output_type -> com.mintter.p2p.v1alpha.RequestInvoiceResponse - 7, // 8: com.mintter.p2p.v1alpha.P2P.CreateSite:output_type -> com.mintter.p2p.v1alpha.CreateSiteResponse - 5, // [5:9] is the sub-list for method output_type - 1, // [1:5] is the sub-list for method input_type + 0, // 4: com.mintter.p2p.v1alpha.P2P.Handshake:output_type -> com.mintter.p2p.v1alpha.HandshakeInfo + 2, // 5: com.mintter.p2p.v1alpha.P2P.ListObjects:output_type -> com.mintter.p2p.v1alpha.ListObjectsResponse + 5, // 6: com.mintter.p2p.v1alpha.P2P.RequestInvoice:output_type -> com.mintter.p2p.v1alpha.RequestInvoiceResponse + 4, // [4:7] is the sub-list for method output_type + 1, // [1:4] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name @@ -665,30 +517,6 @@ func file_p2p_v1alpha_p2p_proto_init() { return nil } } - file_p2p_v1alpha_p2p_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateSiteRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_p2p_v1alpha_p2p_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateSiteResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } } type x struct{} out := protoimpl.TypeBuilder{ @@ -696,7 +524,7 @@ func file_p2p_v1alpha_p2p_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_p2p_v1alpha_p2p_proto_rawDesc, NumEnums: 0, - NumMessages: 8, + NumMessages: 6, NumExtensions: 0, NumServices: 1, }, diff --git a/backend/genproto/p2p/v1alpha/p2p_grpc.pb.go b/backend/genproto/p2p/v1alpha/p2p_grpc.pb.go index 57488cb5e1..239758cf5d 100644 --- a/backend/genproto/p2p/v1alpha/p2p_grpc.pb.go +++ b/backend/genproto/p2p/v1alpha/p2p_grpc.pb.go @@ -34,8 +34,6 @@ type P2PClient interface { ListObjects(ctx context.Context, in *ListObjectsRequest, opts ...grpc.CallOption) (*ListObjectsResponse, error) // Request a peer to issue a lightning BOLT-11 invoice RequestInvoice(ctx context.Context, in *RequestInvoiceRequest, opts ...grpc.CallOption) (*RequestInvoiceResponse, error) - // CreateSite tells a server which group it should serve. - CreateSite(ctx context.Context, in *CreateSiteRequest, opts ...grpc.CallOption) (*CreateSiteResponse, error) } type p2PClient struct { @@ -73,15 +71,6 @@ func (c *p2PClient) RequestInvoice(ctx context.Context, in *RequestInvoiceReques return out, nil } -func (c *p2PClient) CreateSite(ctx context.Context, in *CreateSiteRequest, opts ...grpc.CallOption) (*CreateSiteResponse, error) { - out := new(CreateSiteResponse) - err := c.cc.Invoke(ctx, "/com.mintter.p2p.v1alpha.P2P/CreateSite", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - // P2PServer is the server API for P2P service. // All implementations should embed UnimplementedP2PServer // for forward compatibility @@ -98,8 +87,6 @@ type P2PServer interface { ListObjects(context.Context, *ListObjectsRequest) (*ListObjectsResponse, error) // Request a peer to issue a lightning BOLT-11 invoice RequestInvoice(context.Context, *RequestInvoiceRequest) (*RequestInvoiceResponse, error) - // CreateSite tells a server which group it should serve. - CreateSite(context.Context, *CreateSiteRequest) (*CreateSiteResponse, error) } // UnimplementedP2PServer should be embedded to have forward compatible implementations. @@ -115,9 +102,6 @@ func (UnimplementedP2PServer) ListObjects(context.Context, *ListObjectsRequest) func (UnimplementedP2PServer) RequestInvoice(context.Context, *RequestInvoiceRequest) (*RequestInvoiceResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RequestInvoice not implemented") } -func (UnimplementedP2PServer) CreateSite(context.Context, *CreateSiteRequest) (*CreateSiteResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateSite not implemented") -} // UnsafeP2PServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to P2PServer will @@ -184,24 +168,6 @@ func _P2P_RequestInvoice_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } -func _P2P_CreateSite_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateSiteRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(P2PServer).CreateSite(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/com.mintter.p2p.v1alpha.P2P/CreateSite", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(P2PServer).CreateSite(ctx, req.(*CreateSiteRequest)) - } - return interceptor(ctx, in, info, handler) -} - // P2P_ServiceDesc is the grpc.ServiceDesc for P2P service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -221,10 +187,6 @@ var P2P_ServiceDesc = grpc.ServiceDesc{ MethodName: "RequestInvoice", Handler: _P2P_RequestInvoice_Handler, }, - { - MethodName: "CreateSite", - Handler: _P2P_CreateSite_Handler, - }, }, Streams: []grpc.StreamDesc{}, Metadata: "p2p/v1alpha/p2p.proto", diff --git a/backend/hyper/entity.go b/backend/hyper/entity.go index b577c91134..0bf6fbe38e 100644 --- a/backend/hyper/entity.go +++ b/backend/hyper/entity.go @@ -307,7 +307,7 @@ func NewChange(eid EntityID, deps []cid.Cid, ts hlc.Time, signer core.KeyPair, d return hb, fmt.Errorf("failed to sign change: %w", err) } - hb, err = EncodeBlob(ch.Type, ch) + hb, err = EncodeBlob(ch) if err != nil { return hb, err } diff --git a/backend/hyper/entity_test.go b/backend/hyper/entity_test.go index 37dad76dff..7f3b2eb0f3 100644 --- a/backend/hyper/entity_test.go +++ b/backend/hyper/entity_test.go @@ -27,7 +27,6 @@ func TestEntity(t *testing.T) { }) require.NoError(t, err) - require.Equal(t, TypeChange, ch.Type) require.Nil(t, ch.Decoded.(Change).Deps) name, _ = e.Get("name") diff --git a/backend/hyper/hyper.go b/backend/hyper/hyper.go index 64595f78e9..7dc0b1f743 100644 --- a/backend/hyper/hyper.go +++ b/backend/hyper/hyper.go @@ -15,8 +15,8 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" cbornode "github.com/ipfs/go-ipld-cbor" + dagpb "github.com/ipld/go-codec-dagpb" "github.com/multiformats/go-multicodec" - "github.com/multiformats/go-multihash" "go.uber.org/zap" ) @@ -26,16 +26,27 @@ type BlobType string // Storage is an indexing blob storage. type Storage struct { db *sqlitex.Pool - bs *blockStore + bs *indexingBlockStore log *zap.Logger + + *indexer } // NewStorage creates a new blob storage. func NewStorage(db *sqlitex.Pool, log *zap.Logger) *Storage { - return &Storage{ + bs := newBlockstore(db) + + idx := &indexer{ db: db, - bs: newBlockstore(db), log: log, + bs: bs, + } + + return &Storage{ + db: db, + bs: &indexingBlockStore{blockStore: bs, indexBlob: idx.indexBlob}, + log: log, + indexer: idx, } } @@ -70,8 +81,10 @@ func (bs *Storage) SaveBlob(ctx context.Context, blob Blob) error { } defer release() + codec, hash := ipfs.DecodeCID(blob.CID) + return sqlitex.WithTx(conn, func() error { - id, exists, err := bs.bs.putBlock(conn, 0, uint64(blob.Codec), blob.Hash, blob.Data) + id, exists, err := bs.bs.putBlock(conn, 0, uint64(codec), hash, blob.Data) if err != nil { return err } @@ -81,7 +94,7 @@ func (bs *Storage) SaveBlob(ctx context.Context, blob Blob) error { return nil } - if err := bs.indexBlob(conn, id, blob); err != nil { + if err := bs.indexBlob(conn, id, blob.CID, blob.Decoded); err != nil { return fmt.Errorf("failed to index blob %s: %w", blob.CID, err) } @@ -122,8 +135,10 @@ func (bs *Storage) SaveDraftBlob(ctx context.Context, eid EntityID, blob Blob) e } defer release() + codec, hash := ipfs.DecodeCID(blob.CID) + return sqlitex.WithTx(conn, func() error { - id, exists, err := bs.bs.putBlock(conn, 0, uint64(blob.Codec), blob.Hash, blob.Data) + id, exists, err := bs.bs.putBlock(conn, 0, uint64(codec), hash, blob.Data) if err != nil { return err } @@ -133,7 +148,7 @@ func (bs *Storage) SaveDraftBlob(ctx context.Context, eid EntityID, blob Blob) e return nil } - if err := bs.indexBlob(conn, id, blob); err != nil { + if err := bs.indexBlob(conn, id, blob.CID, blob.Decoded); err != nil { return fmt.Errorf("failed to index blob %s: %w", blob.CID, err) } @@ -283,7 +298,9 @@ func (bs *Storage) ReplaceDraftBlob(ctx context.Context, eid EntityID, old cid.C return err } - id, exists, err := bs.bs.putBlock(conn, oldid, uint64(blob.Codec), blob.Hash, blob.Data) + codec, hash := ipfs.DecodeCID(blob.CID) + + id, exists, err := bs.bs.putBlock(conn, oldid, uint64(codec), hash, blob.Data) if err != nil { return fmt.Errorf("replace draft blob error when insert: %w", err) } @@ -293,7 +310,7 @@ func (bs *Storage) ReplaceDraftBlob(ctx context.Context, eid EntityID, old cid.C return nil } - if err := bs.indexBlob(conn, id, blob); err != nil { + if err := bs.indexBlob(conn, id, blob.CID, blob.Decoded); err != nil { return fmt.Errorf("failed to index blob %s: %w", blob.CID, err) } @@ -343,31 +360,23 @@ func (bs *Storage) IPFSBlockstore() blockstore.Blockstore { // Blob is a structural artifact. type Blob struct { - Type BlobType CID cid.Cid - Codec multicodec.Code - Hash multihash.Multihash Data []byte Decoded any } // EncodeBlob produces a Blob from any object. -func EncodeBlob(t BlobType, v any) (hb Blob, err error) { +func EncodeBlob(v any) (hb Blob, err error) { data, err := cbornode.DumpObject(v) if err != nil { - return hb, fmt.Errorf("failed to encode blob with type %s: %w", t, err) + return hb, fmt.Errorf("failed to encode blob %T: %w", v, err) } - codec := multicodec.DagCbor - - blk := ipfs.NewBlock(uint64(codec), data) + blk := ipfs.NewBlock(uint64(multicodec.DagCbor), data) c := blk.Cid() return Blob{ - Type: t, CID: c, - Codec: codec, - Hash: c.Hash(), Data: data, Decoded: v, }, nil @@ -379,44 +388,109 @@ var errNotHyperBlob = errors.New("not a hyper blob") func DecodeBlob(c cid.Cid, data []byte) (hb Blob, err error) { codec := c.Prefix().Codec - if codec != uint64(multicodec.DagCbor) { - return hb, fmt.Errorf("%s: %w", c, errNotHyperBlob) - } - - var v struct { - Type string `cbor:"@type"` - } - if err := cbor.Unmarshal(data, &v); err != nil { - var vv any - if err := cbornode.DecodeInto(data, &vv); err != nil { - panic(err) + switch multicodec.Code(codec) { + case multicodec.DagPb: + b := dagpb.Type.PBNode.NewBuilder() + if err := dagpb.DecodeBytes(b, data); err != nil { + return hb, fmt.Errorf("failed to decode dagpb node %s: %w", c, err) } - return hb, fmt.Errorf("failed to infer hyper blob %s: %w", c, err) - } - - switch BlobType(v.Type) { - case TypeKeyDelegation: - var v KeyDelegation - if err := cbornode.DecodeInto(data, &v); err != nil { - return hb, err + hb.Decoded = b.Build() + case multicodec.DagCbor: + var v struct { + Type string `cbor:"@type"` } - hb.Decoded = v - case TypeChange: - var v Change - if err := cbornode.DecodeInto(data, &v); err != nil { - return hb, err + if err := cbor.Unmarshal(data, &v); err != nil { + return hb, fmt.Errorf("failed to infer hyper blob %s: %w", c, err) + } + + switch BlobType(v.Type) { + case TypeKeyDelegation: + var v KeyDelegation + if err := cbornode.DecodeInto(data, &v); err != nil { + return hb, err + } + hb.Decoded = v + case TypeChange: + var v Change + if err := cbornode.DecodeInto(data, &v); err != nil { + return hb, err + } + hb.Decoded = v + default: + return hb, fmt.Errorf("unknown hyper blob type: '%s'", v.Type) } - hb.Decoded = v default: - return hb, fmt.Errorf("unknown hyper blob type: '%s'", v.Type) + return hb, fmt.Errorf("%s: %w", c, errNotHyperBlob) } - hb.Type = BlobType(v.Type) hb.CID = c - hb.Codec = multicodec.Code(codec) - hb.Hash = c.Hash() hb.Data = data return hb, nil } + +type indexingBlockStore struct { + *blockStore + indexBlob func(conn *sqlite.Conn, id int64, c cid.Cid, blob any) error +} + +func (b *indexingBlockStore) Put(ctx context.Context, block blocks.Block) error { + conn, release, err := b.db.Conn(ctx) + if err != nil { + return err + } + defer release() + + return sqlitex.WithTx(conn, func() error { + codec, hash := ipfs.DecodeCID(block.Cid()) + id, exists, err := b.putBlock(conn, 0, codec, hash, block.RawData()) + if err != nil { + return err + } + + if exists || !isIndexable(multicodec.Code(codec)) { + return nil + } + + hb, err := DecodeBlob(block.Cid(), block.RawData()) + if err != nil { + return err + } + return b.indexBlob(conn, id, hb.CID, hb.Decoded) + }) +} + +// PutMany implements blockstore.Blockstore interface. +func (b *indexingBlockStore) PutMany(ctx context.Context, blocks []blocks.Block) error { + conn, release, err := b.db.Conn(ctx) + if err != nil { + return err + } + defer release() + + return sqlitex.WithTx(conn, func() error { + for _, blk := range blocks { + codec, hash := ipfs.DecodeCID(blk.Cid()) + id, exists, err := b.putBlock(conn, 0, codec, hash, blk.RawData()) + if err != nil { + return err + } + + if exists || !isIndexable(multicodec.Code(codec)) { + continue + } + + hb, err := DecodeBlob(blk.Cid(), blk.RawData()) + if err != nil { + return err + } + + if err := b.indexBlob(conn, id, hb.CID, hb.Decoded); err != nil { + return err + } + } + + return nil + }) +} diff --git a/backend/hyper/hypersql/queries.manual.go b/backend/hyper/hypersql/queries.manual.go index 97c9cd7194..1e0bdc7570 100644 --- a/backend/hyper/hypersql/queries.manual.go +++ b/backend/hyper/hypersql/queries.manual.go @@ -3,6 +3,7 @@ package hypersql import ( "fmt" "mintter/backend/daemon/storage" + "mintter/backend/pkg/dqb" "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" @@ -158,3 +159,21 @@ func GroupGetRole(conn *sqlite.Conn, resource, owner, member int64) (int64, erro return role, nil } + +// SitesInsertOrIgnore inserts a site if it doesn't exist. +func SitesInsertOrIgnore(conn *sqlite.Conn, baseURL, groupID string) error { + return sqlitex.Exec(conn, qSitesInsertOrIgnore(), nil, baseURL, groupID) +} + +var qSitesInsertOrIgnore = dqb.Str(` + INSERT OR IGNORE INTO remote_sites (url, group_id) VALUES (?, ?); +`) + +// AccountsInsertOrIgnore inserts an account if it doesn't exist. +func AccountsInsertOrIgnore(conn *sqlite.Conn, entity, publicKey int64) error { + return sqlitex.Exec(conn, qAccountsInsertOrIgnore(), nil, entity, publicKey) +} + +var qAccountsInsertOrIgnore = dqb.Str(` + INSERT OR IGNORE INTO accounts (entity, public_key) VALUES (?, ?); +`) diff --git a/backend/hyper/indexing.go b/backend/hyper/indexing.go index 4a2834d039..ac821fdd3c 100644 --- a/backend/hyper/indexing.go +++ b/backend/hyper/indexing.go @@ -18,14 +18,24 @@ import ( "crawshaw.io/sqlite/sqlitex" "github.com/ipfs/go-cid" cbornode "github.com/ipfs/go-ipld-cbor" + dagpb "github.com/ipld/go-codec-dagpb" + "github.com/ipld/go-ipld-prime" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/ipld/go-ipld-prime/traversal" "github.com/multiformats/go-multicodec" "go.uber.org/zap" "golang.org/x/exp/slices" "google.golang.org/protobuf/encoding/protojson" ) +type indexer struct { + db *sqlitex.Pool + log *zap.Logger + bs *blockStore +} + // Reindex forces deletes all the information derived from the blobs and reindexes them. -func (bs *Storage) Reindex(ctx context.Context) (err error) { +func (bs *indexer) Reindex(ctx context.Context) (err error) { conn, release, err := bs.db.Conn(ctx) if err != nil { return err @@ -35,7 +45,7 @@ func (bs *Storage) Reindex(ctx context.Context) (err error) { return bs.reindex(conn) } -func (bs *Storage) reindex(conn *sqlite.Conn) (err error) { +func (bs *indexer) reindex(conn *sqlite.Conn) (err error) { start := time.Now() bs.log.Debug("ReindexingStarted") defer func() { @@ -61,8 +71,8 @@ func (bs *Storage) reindex(conn *sqlite.Conn) (err error) { buf := make([]byte, 0, 1024*1024) // 1MB preallocated slice to reuse for decompressing. if err := sqlitex.ExecTransient(conn, q, func(stmt *sqlite.Stmt) error { codec := stmt.ColumnInt64(stmt.ColumnIndex(storage.BlobsCodec.ShortName())) - // We only know how to index dag-cbor blobs. - if codec != int64(multicodec.DagCbor) { + + if !isIndexable(multicodec.Code(codec)) { return nil } @@ -85,7 +95,7 @@ func (bs *Storage) reindex(conn *sqlite.Conn) (err error) { return nil } - return bs.indexBlob(conn, id, hb) + return bs.indexBlob(conn, id, hb.CID, hb.Decoded) }); err != nil { return err } @@ -99,7 +109,7 @@ func (bs *Storage) reindex(conn *sqlite.Conn) (err error) { } // MaybeReindex will trigger reindexing if it's needed. -func (bs *Storage) MaybeReindex(ctx context.Context) error { +func (bs *indexer) MaybeReindex(ctx context.Context) error { conn, release, err := bs.db.Conn(ctx) if err != nil { return err @@ -121,140 +131,224 @@ func (bs *Storage) MaybeReindex(ctx context.Context) error { // indexBlob is an uber-function that knows about all types of blobs we want to index. // This is probably a bad idea to put here, but for now it's easier to work with that way. // TODO(burdiyan): eventually we might want to make this package agnostic to blob types. -func (bs *Storage) indexBlob(conn *sqlite.Conn, id int64, blob Blob) error { - switch v := blob.Decoded.(type) { +func (bs *indexer) indexBlob(conn *sqlite.Conn, id int64, c cid.Cid, blobData any) error { + switch v := blobData.(type) { + case ipld.Node: + return bs.indexDagPB(conn, id, c, v) case KeyDelegation: - // Validate key delegation. - { - if v.Purpose != DelegationPurposeRegistration { - return fmt.Errorf("unknown key delegation purpose %q", v.Purpose) - } + return bs.indexKeyDelegation(conn, id, c, v) + case Change: + return bs.indexChange(conn, id, c, v) + } - if _, err := v.Issuer.Libp2pKey(); err != nil { - return fmt.Errorf("key delegation issuer is not a valid libp2p public key: %w", err) - } + return nil +} - if _, err := v.Delegate.Libp2pKey(); err != nil { - return fmt.Errorf("key delegation delegate is not a valid libp2p public key: %w", err) - } +func (bs *indexer) indexDagPB(conn *sqlite.Conn, id int64, c cid.Cid, v ipld.Node) error { + return traversal.WalkLocal(v, func(prog traversal.Progress, n ipld.Node) error { + pblink, ok := n.(dagpb.PBLink) + if !ok { + return nil } - iss, err := hypersql.LookupEnsure(conn, storage.LookupPublicKey, v.Issuer) - if err != nil { - return err + c, ok := pblink.Hash.Link().(cidlink.Link) + if !ok { + return fmt.Errorf("link is not CID: %v", pblink.Hash) } - del, err := hypersql.LookupEnsure(conn, storage.LookupPublicKey, v.Delegate) - if err != nil { - return err + rel := "dagpb/chunk" + if pblink.Name.Exists() { + rel = "dagpb/" + pblink.Name.Must().String() } - // We know issuer is an account when delegation purpose is registration. - accEntity := EntityID("hm://a/" + v.Issuer.String()) - if _, err := hypersql.LookupEnsure(conn, storage.LookupResource, accEntity); err != nil { + target, err := bs.ensureBlob(conn, c.Cid) + if err != nil { return err } - if err := hypersql.BlobAttrsInsert(conn, id, "kd/issuer", "", true, iss, nil, 0); err != nil { - return err + return hypersql.BlobLinksInsertOrIgnore(conn, id, rel, target) + }) +} + +func (bs *indexer) indexKeyDelegation(conn *sqlite.Conn, id int64, c cid.Cid, v KeyDelegation) error { + // Validate key delegation. + { + if v.Purpose != DelegationPurposeRegistration { + return fmt.Errorf("unknown key delegation purpose %q", v.Purpose) } - if err := hypersql.BlobAttrsInsert(conn, id, "kd/delegate", "", true, del, nil, 0); err != nil { - return err + if _, err := v.Issuer.Libp2pKey(); err != nil { + return fmt.Errorf("key delegation issuer is not a valid libp2p public key: %w", err) } - case Change: - // TODO(burdiyan): ensure there's only one change that brings an entity into life. - iss, err := hypersql.KeyDelegationsGetIssuer(conn, v.Delegation.Hash()) - if err != nil { - return err + if _, err := v.Delegate.Libp2pKey(); err != nil { + return fmt.Errorf("key delegation delegate is not a valid libp2p public key: %w", err) } - if iss.KeyDelegationsIssuer == 0 { - // Try to get the issuer from the actual blob. This can happen when we are reindexing all the blobs, - // and we happen to index a change before the key delegation. + } - blk, err := bs.bs.get(conn, v.Delegation) - if err != nil { - return err - } + iss, err := hypersql.LookupEnsure(conn, storage.LookupPublicKey, v.Issuer) + if err != nil { + return err + } - var del KeyDelegation - if err := cbornode.DecodeInto(blk.RawData(), &del); err != nil { - return fmt.Errorf("failed to decode key delegation when indexing change %s: %w", blob.CID, err) - } + del, err := hypersql.LookupEnsure(conn, storage.LookupPublicKey, v.Delegate) + if err != nil { + return err + } - iss.KeyDelegationsIssuer, err = bs.ensurePublicKey(conn, del.Issuer) - if err != nil { - return err - } + // We know issuer is an account when delegation purpose is registration. + accEntity := EntityID("hm://a/" + v.Issuer.String()) + edb, err := hypersql.LookupEnsure(conn, storage.LookupResource, accEntity) + if err != nil { + return err + } - if iss.KeyDelegationsIssuer == 0 { - return fmt.Errorf("missing key delegation info %s of change %s", v.Delegation, blob.CID) - } - } + if err := hypersql.AccountsInsertOrIgnore(conn, edb, iss); err != nil { + return err + } - // TODO(burdiyan): remove this when all the tests are fixed. Sometimes CBOR codec decodes into - // different types than what was encoded, and we might not have accounted for that during indexing. - // So we re-encode the patch here to make sure. - // This is of course very wasteful. - { - data, err := cbornode.DumpObject(v.Patch) - if err != nil { - return err - } - v.Patch = nil + if err := hypersql.BlobAttrsInsert(conn, id, "kd/issuer", "", true, iss, nil, 0); err != nil { + return err + } - if err := cbornode.DecodeInto(data, &v.Patch); err != nil { - return err - } - } + if err := hypersql.BlobAttrsInsert(conn, id, "kd/delegate", "", true, del, nil, 0); err != nil { + return err + } - isspk, err := hypersql.PublicKeysLookupPrincipal(conn, iss.KeyDelegationsIssuer) + return nil +} + +func (bs *indexer) indexChange(conn *sqlite.Conn, id int64, c cid.Cid, v Change) error { + // TODO(burdiyan): ensure there's only one change that brings an entity into life. + + iss, err := hypersql.KeyDelegationsGetIssuer(conn, v.Delegation.Hash()) + if err != nil { + return err + } + if iss.KeyDelegationsIssuer == 0 { + // Try to get the issuer from the actual blob. This can happen when we are reindexing all the blobs, + // and we happen to index a change before the key delegation. + + blk, err := bs.bs.get(conn, v.Delegation) if err != nil { return err } - // ensure entity - eid, err := bs.ensureEntity(conn, v.Entity) + var del KeyDelegation + if err := cbornode.DecodeInto(blk.RawData(), &del); err != nil { + return fmt.Errorf("failed to decode key delegation when indexing change %s: %w", c, err) + } + + iss.KeyDelegationsIssuer, err = bs.ensurePublicKey(conn, del.Issuer) if err != nil { return err } - if err := hypersql.BlobAttrsInsert(conn, id, "resource/id", "", true, eid, nil, v.HLCTime.Pack()); err != nil { - return err + if iss.KeyDelegationsIssuer == 0 { + return fmt.Errorf("missing key delegation info %s of change %s", v.Delegation, c) } + } - for _, dep := range v.Deps { - res, err := hypersql.BlobsGetSize(conn, dep.Hash()) - if err != nil { - return err - } - if res.BlobsSize < 0 || res.BlobsID == 0 { - return fmt.Errorf("missing causal dependency %s of change %s", dep, blob.CID) - } + delid, err := hypersql.BlobsGetSize(conn, v.Delegation.Hash()) + if err != nil { + return err + } + if delid.BlobsID == 0 { + return fmt.Errorf("missing key delegation %s of change %s", v.Delegation, c) + } - if err := hypersql.BlobLinksInsertOrIgnore(conn, id, "change/dep", res.BlobsID); err != nil { - return fmt.Errorf("failed to link dependency %s of change %s: %w", dep, blob.CID, err) - } + if err := hypersql.BlobLinksInsertOrIgnore(conn, id, "change/auth", delid.BlobsID); err != nil { + return fmt.Errorf("failed to link key delegation %s of change %s: %w", v.Delegation, c, err) + } + + // TODO(burdiyan): remove this when all the tests are fixed. Sometimes CBOR codec decodes into + // different types than what was encoded, and we might not have accounted for that during indexing. + // So we re-encode the patch here to make sure. + // This is of course very wasteful. + { + data, err := cbornode.DumpObject(v.Patch) + if err != nil { + return err } + v.Patch = nil - if err := hypersql.ChangesInsertOrIgnore(conn, id, eid, v.HLCTime.Pack(), iss.KeyDelegationsIssuer); err != nil { + if err := cbornode.DecodeInto(data, &v.Patch); err != nil { return err } + } + + isspk, err := hypersql.PublicKeysLookupPrincipal(conn, iss.KeyDelegationsIssuer) + if err != nil { + return err + } + + // ensure entity + eid, err := bs.ensureEntity(conn, v.Entity) + if err != nil { + return err + } - if v.Entity.HasPrefix("hm://d/") { - return bs.indexDocumentChange(conn, id, isspk.PublicKeysPrincipal, blob.CID, v) + if err := hypersql.BlobAttrsInsert(conn, id, "resource/id", "", true, eid, nil, v.HLCTime.Pack()); err != nil { + return err + } + + for _, dep := range v.Deps { + res, err := hypersql.BlobsGetSize(conn, dep.Hash()) + if err != nil { + return err + } + if res.BlobsSize < 0 || res.BlobsID == 0 { + return fmt.Errorf("missing causal dependency %s of change %s", dep, c) } - if v.Entity.HasPrefix("hm://g/") { - return bs.indexGroupChange(conn, id, isspk.PublicKeysPrincipal, blob.CID, v) + if err := hypersql.BlobLinksInsertOrIgnore(conn, id, "change/dep", res.BlobsID); err != nil { + return fmt.Errorf("failed to link dependency %s of change %s: %w", dep, c, err) } } + if err := hypersql.ChangesInsertOrIgnore(conn, id, eid, v.HLCTime.Pack(), iss.KeyDelegationsIssuer); err != nil { + return err + } + + if v.Entity.HasPrefix("hm://d/") { + return bs.indexDocumentChange(conn, id, isspk.PublicKeysPrincipal, c, v) + } + + if v.Entity.HasPrefix("hm://g/") { + return bs.indexGroupChange(conn, id, isspk.PublicKeysPrincipal, c, v) + } + + if v.Entity.HasPrefix("hm://a/") { + return bs.indexAccountChange(conn, id, isspk.PublicKeysPrincipal, c, v) + } + return nil } -func (bs *Storage) ensureEntity(conn *sqlite.Conn, eid EntityID) (int64, error) { +func (bs *indexer) ensureBlob(conn *sqlite.Conn, c cid.Cid) (int64, error) { + codec, hash := ipfs.DecodeCID(c) + + size, err := hypersql.BlobsGetSize(conn, hash) + if err != nil { + return 0, err + } + + if size.BlobsID != 0 { + return size.BlobsID, nil + } + + ins, err := hypersql.BlobsInsert(conn, 0, hash, int64(codec), nil, -1) + if err != nil { + return 0, err + } + if ins.BlobsID == 0 { + return 0, fmt.Errorf("failed to ensure blob %s after insert", c) + } + + return ins.BlobsID, nil +} + +func (bs *indexer) ensureEntity(conn *sqlite.Conn, eid EntityID) (int64, error) { look, err := hypersql.EntitiesLookupID(conn, string(eid)) if err != nil { return 0, err @@ -274,7 +368,7 @@ func (bs *Storage) ensureEntity(conn *sqlite.Conn, eid EntityID) (int64, error) return ins.EntitiesID, nil } -func (bs *Storage) ensurePublicKey(conn *sqlite.Conn, key core.Principal) (int64, error) { +func (bs *indexer) ensurePublicKey(conn *sqlite.Conn, key core.Principal) (int64, error) { res, err := hypersql.PublicKeysLookupID(conn, key) if err != nil { return 0, err @@ -296,7 +390,7 @@ func (bs *Storage) ensurePublicKey(conn *sqlite.Conn, key core.Principal) (int64 return ins.PublicKeysID, nil } -func (bs *Storage) indexGroupChange(conn *sqlite.Conn, blobID int64, author core.Principal, c cid.Cid, ch Change) error { +func (bs *indexer) indexGroupChange(conn *sqlite.Conn, blobID int64, author core.Principal, c cid.Cid, ch Change) error { hlc := ch.HLCTime.Pack() // Validate group change. @@ -394,29 +488,53 @@ func (bs *Storage) indexGroupChange(conn *sqlite.Conn, blobID int64, author core } if ch.Patch["members"] != nil && !isOwner { - return fmt.Errorf("group members can only be updated by an owner") + return fmt.Errorf("group members can only be updated by the owner") + } + + if ch.Patch["siteURL"] != nil && !isOwner { + return fmt.Errorf("group siteURL can only be updated by the owner") } default: return fmt.Errorf("unknown group action %q", ch.Action) } } - title, ok := ch.Patch["title"].(string) - if ok { + if title, ok := ch.Patch["title"].(string); ok { if err := hypersql.BlobAttrsInsert(conn, blobID, "resource/title", "", false, title, nil, hlc); err != nil { return err } } - desc, ok := ch.Patch["description"].(string) - if ok { + if desc, ok := ch.Patch["description"].(string); ok { if err := hypersql.BlobAttrsInsert(conn, blobID, "resource/description", "", false, desc, nil, hlc); err != nil { return err } } - content, ok := ch.Patch["content"].(map[string]any) - if ok { + if siteURL, ok := ch.Patch["siteURL"].(string); ok { + u, err := url.Parse(siteURL) + if err != nil { + return fmt.Errorf("failed to parse site URL %s: %w", siteURL, err) + } + + if u.Scheme != "http" && u.Scheme != "https" { + return fmt.Errorf("site URL must have http or https scheme, got %s", siteURL) + } + + if siteURL != (&url.URL{Scheme: u.Scheme, Host: u.Host}).String() { + return fmt.Errorf("site URL must have only scheme and host, got %s", siteURL) + } + + if err := hypersql.BlobAttrsInsert(conn, blobID, "group/site-url", "", false, siteURL, nil, hlc); err != nil { + return err + } + + if err := hypersql.SitesInsertOrIgnore(conn, siteURL, string(ch.Entity)); err != nil { + return err + } + } + + if content, ok := ch.Patch["content"].(map[string]any); ok { for path, v := range content { rawURL, ok := v.(string) if !ok { @@ -430,8 +548,7 @@ func (bs *Storage) indexGroupChange(conn *sqlite.Conn, blobID int64, author core } } - members, ok := ch.Patch["members"].(map[string]any) - if ok { + if members, ok := ch.Patch["members"].(map[string]any); ok { for k, v := range members { acc, err := core.DecodePrincipal(k) if err != nil { @@ -461,7 +578,52 @@ func (bs *Storage) indexGroupChange(conn *sqlite.Conn, blobID int64, author core return nil } -func (bs *Storage) indexDocumentChange(conn *sqlite.Conn, blobID int64, author core.Principal, c cid.Cid, ch Change) error { +func (bs *indexer) indexAccountChange(conn *sqlite.Conn, blobID int64, author core.Principal, c cid.Cid, ch Change) error { + if "hm://a/"+author.String() != string(ch.Entity) { + return fmt.Errorf("author %s is not allowed to modify account entity %s", author.String(), ch.Entity) + } + + if ch.Patch == nil { + return fmt.Errorf("account changes must have patches") + } + + pkdb, err := hypersql.LookupEnsure(conn, storage.LookupPublicKey, author) + if err != nil { + return err + } + + edb, err := hypersql.LookupEnsure(conn, storage.LookupResource, ch.Entity) + if err != nil { + return err + } + + if err := hypersql.AccountsInsertOrIgnore(conn, edb, pkdb); err != nil { + return err + } + + hlc := ch.HLCTime.Pack() + + if v, ok := ch.Patch["avatar"].(cid.Cid); ok { + target, err := bs.ensureBlob(conn, v) + if err != nil { + return err + } + + if err := hypersql.BlobLinksInsertOrIgnore(conn, blobID, "href/avatar", target); err != nil { + return err + } + } + + if v, ok := ch.Patch["alias"].(string); ok { + if err := hypersql.BlobAttrsInsert(conn, blobID, "account/alias", "", false, v, nil, hlc); err != nil { + return err + } + } + + return nil +} + +func (bs *indexer) indexDocumentChange(conn *sqlite.Conn, blobID int64, author core.Principal, c cid.Cid, ch Change) error { hlc := ch.HLCTime.Pack() // Validate document change. @@ -579,7 +741,7 @@ type LinkData struct { TargetVersion string `json:"v,omitempty"` } -func (bs *Storage) indexURL(conn *sqlite.Conn, blobID int64, key, anchor, rawURL string, ts int64) error { +func (bs *indexer) indexURL(conn *sqlite.Conn, blobID int64, key, anchor, rawURL string, ts int64) error { if rawURL == "" { return nil } @@ -645,8 +807,37 @@ func (bs *Storage) indexURL(conn *sqlite.Conn, blobID int64, key, anchor, rawURL } } case "ipfs": - // TODO: parse ipfs links + c, err := cid.Decode(u.Hostname()) + if err != nil { + return fmt.Errorf("failed to parse IPFS URL %s: %w", rawURL, err) + } + + target, err := bs.ensureBlob(conn, c) + if err != nil { + return err + } + + if err := hypersql.BlobLinksInsertOrIgnore(conn, blobID, key, target); err != nil { + return err + } } return nil } + +type indexData struct { + Blob cid.Cid +} + +func isIndexable[T multicodec.Code | cid.Cid](v T) bool { + var code multicodec.Code + + switch v := any(v).(type) { + case multicodec.Code: + code = v + case cid.Cid: + code = multicodec.Code(v.Prefix().Codec) + } + + return code == multicodec.DagCbor || code == multicodec.DagPb +} diff --git a/backend/hyper/terra.go b/backend/hyper/terra.go index 4d16a84c79..f2b0723dc9 100644 --- a/backend/hyper/terra.go +++ b/backend/hyper/terra.go @@ -113,7 +113,7 @@ func (kd KeyDelegation) Verify() error { // Blob encodes the delegation into a blob. func (kd KeyDelegation) Blob() Blob { - hb, err := EncodeBlob(TypeKeyDelegation, kd) + hb, err := EncodeBlob(kd) if err != nil { panic(err) } diff --git a/backend/ipfs/libp2p.go b/backend/ipfs/libp2p.go index 1db31bb55e..92495e56f6 100644 --- a/backend/ipfs/libp2p.go +++ b/backend/ipfs/libp2p.go @@ -301,3 +301,14 @@ func DefaultListenAddrs(port int) []string { "/ip6/::/udp/" + portstr + "/quic-v1/webtransport", } } + +// DefaultListenAddrsDNS creates the default listening addresses for a DNS name and port. +func DefaultListenAddrsDNS(hostname string, port int) []string { + portstr := strconv.Itoa(port) + return []string{ + "/dns4/" + hostname + "/tcp/" + portstr, + "/dns4/" + hostname + "/udp/" + portstr + "/quic", + "/dns4/" + hostname + "/udp/" + portstr + "/quic-v1", + "/dns4/" + hostname + "/udp/" + portstr + "/quic-v1/webtransport", + } +} diff --git a/backend/lndhub/lndhub_test.go b/backend/lndhub/lndhub_test.go index 69f397e832..331a9c4ef7 100644 --- a/backend/lndhub/lndhub_test.go +++ b/backend/lndhub/lndhub_test.go @@ -45,17 +45,17 @@ func TestCreate(t *testing.T) { defer cancel() identity := future.New[core.Identity]() lndHubClient := NewClient(context.Background(), &http.Client{}, pool, identity.ReadOnly, mintterDomain, lnaddressDomain) - keypair, err := core.NewKeyPairRandom(core.CodecDeviceKey) + keypair, err := core.NewKeyPairRandom() require.NoError(t, err) priv, pub, err := crypto.GenerateEd25519Key(nil) require.NoError(t, err) pubkeyBytes, err := pub.Raw() require.NoError(t, err) - pubkey, err := core.NewPublicKey(core.CodecAccountKey, pub.(*crypto.Ed25519PublicKey)) + pubkey, err := core.NewPublicKey(pub.(*crypto.Ed25519PublicKey)) require.NoError(t, err) - login := pubkey.CID().String() + login := pubkey.Principal().String() passwordBytes, err := priv.Sign([]byte(SigninMessage)) password := hex.EncodeToString(passwordBytes) require.NoError(t, err) diff --git a/backend/mttnet/client.go b/backend/mttnet/client.go index 4eb0b8b44e..a4cc0d6735 100644 --- a/backend/mttnet/client.go +++ b/backend/mttnet/client.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "mintter/backend/core" - site "mintter/backend/genproto/documents/v1alpha" p2p "mintter/backend/genproto/p2p/v1alpha" "net" "sync" @@ -67,16 +66,6 @@ func (c *Client) Dial(ctx context.Context, pid peer.ID) (p2p.P2PClient, error) { return p2p.NewP2PClient(conn), nil } -// DialSite dials a remote site a remote peer and provide and RPC client instance. -func (c *Client) DialSite(ctx context.Context, siteDeviceID peer.ID) (site.WebSiteClient, error) { - conn, err := c.dialPeer(ctx, siteDeviceID) - if err != nil { - return nil, err - } - - return site.NewWebSiteClient(conn), nil -} - // Close the Client and all the open connections and streams gracefully. func (c *Client) Close() (err error) { c.mu.Lock() diff --git a/backend/mttnet/connect.go b/backend/mttnet/connect.go index a5b473350b..9efccdd5ff 100644 --- a/backend/mttnet/connect.go +++ b/backend/mttnet/connect.go @@ -156,11 +156,8 @@ func (n *Node) verifyHandshake(ctx context.Context, pid peer.ID, pb *p2p.Handsha var errDialSelf = errors.New("can't dial self") // Handshake gets called by the remote peer who initiates the connection. -func (srv *Server) Handshake(ctx context.Context, in *p2p.HandshakeInfo) (*p2p.HandshakeInfo, error) { - n, ok := srv.Node.Get() - if !ok { - return nil, fmt.Errorf("Node not ready yet") - } +func (srv *rpcMux) Handshake(ctx context.Context, in *p2p.HandshakeInfo) (*p2p.HandshakeInfo, error) { + n := srv.Node info, ok := rpcpeer.FromContext(ctx) if !ok { panic("BUG: no peer info in context for grpc") diff --git a/backend/mttnet/list_objects.go b/backend/mttnet/list_objects.go index 70bdb464d4..d867f86413 100644 --- a/backend/mttnet/list_objects.go +++ b/backend/mttnet/list_objects.go @@ -2,56 +2,18 @@ package mttnet import ( "context" - "fmt" - site "mintter/backend/genproto/documents/v1alpha" p2p "mintter/backend/genproto/p2p/v1alpha" "mintter/backend/hyper" "mintter/backend/hyper/hypersql" - "mintter/backend/mttnet/sitesql" "crawshaw.io/sqlite" "github.com/ipfs/go-cid" - "go.uber.org/zap" "golang.org/x/exp/maps" ) // ListObjects lists all the local objects. -func (srv *Server) ListObjects(ctx context.Context, in *p2p.ListObjectsRequest) (*p2p.ListObjectsResponse, error) { - n, err := srv.Node.Await(ctx) - if err != nil { - return nil, fmt.Errorf("node is not ready yet: %w", err) - } - - if n.cfg.NoListing && srv.Site.hostname == "" { - return &p2p.ListObjectsResponse{}, nil - } - - if srv.Site.hostname != "" { //means this is a site. Sites don't sync out to every peer. Only peers that are subscribed to that site if NoLIsting is true - remoteDeviceID, err := getRemoteID(ctx) - if err != nil { - n.log.Warn("Couldn't get remote caller in ListObjects.", zap.Error(err)) - return nil, fmt.Errorf("couldn't get account ID from device [%s]: %w", remoteDeviceID.String(), err) - } - - remotAcc, err := n.AccountForDevice(ctx, remoteDeviceID) - if err != nil { - return nil, fmt.Errorf("couldn't get account ID from device [%s]: %w", remoteDeviceID.String(), err) - } - - conn, cancel, err := n.db.Conn(ctx) - if err != nil { - return nil, fmt.Errorf("Cannot connect to internal db: %w", err) - } - defer cancel() - - role, err := sitesql.GetMemberRole(conn, remotAcc) - if (err != nil || role == site.Member_ROLE_UNSPECIFIED) && n.cfg.NoListing { - n.log.Debug("Not serving content to remote peer since is not a site member", zap.String("remote AccountID", remotAcc.String()), - zap.Error(err), zap.Int("Role", int(role))) - return &p2p.ListObjectsResponse{}, nil - } - n.log.Debug("Allowing site content", zap.String("remote AccountID", remotAcc.String()), zap.Int("role", int(role)), zap.Bool("noListing", n.cfg.NoListing)) - } +func (srv *rpcMux) ListObjects(ctx context.Context, in *p2p.ListObjectsRequest) (*p2p.ListObjectsResponse, error) { + n := srv.Node objs := map[hyper.EntityID]*p2p.Object{} diff --git a/backend/mttnet/mttnet.go b/backend/mttnet/mttnet.go index acc08fda75..fd9413c151 100644 --- a/backend/mttnet/mttnet.go +++ b/backend/mttnet/mttnet.go @@ -3,22 +3,17 @@ package mttnet import ( "context" - "crypto/rand" - "encoding/base64" "fmt" "io" "mintter/backend/config" "mintter/backend/core" - site "mintter/backend/genproto/documents/v1alpha" + groups "mintter/backend/genproto/groups/v1alpha" p2p "mintter/backend/genproto/p2p/v1alpha" "mintter/backend/hyper" "mintter/backend/hyper/hypersql" "mintter/backend/ipfs" - "mintter/backend/mttnet/sitesql" "mintter/backend/pkg/cleanup" - "mintter/backend/pkg/future" "mintter/backend/pkg/must" - "strings" "time" "crawshaw.io/sqlite" @@ -56,6 +51,18 @@ const ( var userAgent = "mintter/" +// WebsiteClient is the bridge to talk to remote sites. +type WebsiteClient interface { + // InitializeServer instruct the website that starts serving a given group. + InitializeServer(context.Context, *groups.InitializeServerRequest, ...grpc.CallOption) (*groups.InitializeServerResponse, error) + + // GetSiteInfo gets public site information, to be also found in /.well-known/hypermedia-site + GetSiteInfo(context.Context, *groups.GetSiteInfoRequest, ...grpc.CallOption) (*groups.PublicSiteInfo, error) + + // PublishBlobs pushes given blobs to the site. + PublishBlobs(context.Context, *groups.PublishBlobsRequest, ...grpc.CallOption) (*groups.PublishBlobsResponse, error) +} + // DefaultRelays bootstrap mintter-owned relays so they can reserve slots to do holepunch. func DefaultRelays() []peer.AddrInfo { return []peer.AddrInfo{ @@ -91,20 +98,9 @@ type PublicationRecord struct { References []docInfo } -// Site is a hosted site. -type Site struct { - hostname string - InviteTokenExpirationDelay time.Duration - owner core.Principal -} - // Server holds the p2p functionality to be accessed via gRPC. -type Server struct { - Node *future.ReadOnly[*Node] - *Site - localFunctions DocumentsAPI - synchronizer Synchronizer - NoAuth bool +type rpcMux struct { + Node *Node } // Node is a Mintter P2P node. @@ -117,21 +113,13 @@ type Node struct { invoicer Invoicer client *Client - p2p *ipfs.Libp2p - bitswap *ipfs.Bitswap - providing provider.System - grpc *grpc.Server - quit io.Closer - ready chan struct{} - registered chan struct{} - ctx context.Context // will be set after calling Start() -} - -// DocumentsAPI is an interface for not having to pass a full-fledged documents service, -// just the getPublication that is what we need to call in getPath. -type DocumentsAPI interface { - // GetPublication gets a local publication. - GetPublication(ctx context.Context, in *site.GetPublicationRequest) (*site.Publication, error) + p2p *ipfs.Libp2p + bitswap *ipfs.Bitswap + providing provider.System + grpc *grpc.Server + quit io.Closer + ready chan struct{} + ctx context.Context // will be set after calling Start() } // Synchronizer is a subset of the syncing service that @@ -140,110 +128,9 @@ type Synchronizer interface { SyncWithPeer(ctx context.Context, device peer.ID, initialObjects ...hyper.EntityID) error } -// NewServer returns a new mttnet API server. -func NewServer(ctx context.Context, siteCfg config.Site, node *future.ReadOnly[*Node], docSrv DocumentsAPI, sync Synchronizer) *Server { - expirationDelay := siteCfg.InviteTokenExpirationDelay - - srv := &Server{ - Site: &Site{ - hostname: siteCfg.Hostname, - InviteTokenExpirationDelay: expirationDelay, - }, - Node: node, - localFunctions: docSrv, - synchronizer: sync, - NoAuth: siteCfg.NoAuth, - } - - if siteCfg.OwnerID != "" { - owner, err := core.DecodePrincipal(siteCfg.OwnerID) - if err != nil { - panic(fmt.Errorf("BUG: failed to parse owner ID: %w", err)) - } - - srv.owner = owner - } - - go func() { - n, err := node.Await(ctx) - if err != nil { - return - } - - // this is how we respond to remote RPCs over libp2p. - p2p.RegisterP2PServer(n.grpc, srv) - site.RegisterWebSiteServer(n.grpc, srv) - - if siteCfg.Hostname != "" { - if srv.owner == nil { - srv.owner = n.me.Account().Principal() - } - - conn, release, err := n.db.Conn(ctx) - if err != nil { - panic(err) - } - defer release() - if err := func() error { - randomBytes := make([]byte, 16) - _, err := rand.Read(randomBytes) - if err != nil { - return err - } - currentLink, err := sitesql.GetSiteRegistrationLink(conn) - if err != nil { - return err - } - link := currentLink.KVValue - if link == "" || siteCfg.Hostname != strings.Split(link, "/secret-invite/")[0] { - link = siteCfg.Hostname + "/secret-invite/" + base64.RawURLEncoding.EncodeToString(randomBytes) - if err := sitesql.SetSiteRegistrationLink(conn, link); err != nil { - return err - } - } - - // Print it to stdout so its visible from the command line. - fmt.Println("Site Invitation secret token: " + link) - if siteCfg.Title != "" { - title, err := sitesql.GetSiteTitle(conn) - if err != nil { - return err - } - - if title.KVValue != siteCfg.Title { - if err := sitesql.SetSiteTitle(conn, siteCfg.Title); err != nil { - return err - } - } - } - - if err := srv.updateSiteBio(ctx, siteCfg.Title, "Mintter Site"); err != nil { - return err - } - - if _, err := sitesql.AddMember(conn, srv.owner, int64(site.Member_OWNER)); err != nil { - // This is equivalent of INSERT OR IGNORE, but we don't use it because in other places where - // we call this function we do care about the errors. - if sqlite.ErrCode(err) != sqlite.SQLITE_CONSTRAINT_PRIMARYKEY { - return err - } - } - - return nil - }(); err != nil { - panic(err) - } - } - - // Indicate we can now serve the already registered endpoints. - close(n.registered) - }() - return srv -} - // New creates a new P2P Node. The users must call Start() before using the node, and can use Ready() to wait // for when the node is ready to use. -func New(cfg config.P2P, db *sqlitex.Pool, blobs *hyper.Storage, me core.Identity, log *zap.Logger) (*Node, error) { +func New(cfg config.P2P, db *sqlitex.Pool, blobs *hyper.Storage, me core.Identity, log *zap.Logger, extraServers ...interface{}) (*Node, error) { var clean cleanup.Stack host, closeHost, err := newLibp2p(cfg, me.DeviceKey().Wrapped(), db) @@ -269,19 +156,28 @@ func New(cfg config.P2P, db *sqlitex.Pool, blobs *hyper.Storage, me core.Identit clean.Add(client) n := &Node{ - log: log, - blobs: blobs, - db: db, - me: me, - cfg: cfg, - client: client, - p2p: host, - bitswap: bitswap, - providing: providing, - grpc: grpc.NewServer(), - quit: &clean, - ready: make(chan struct{}), - registered: make(chan struct{}), + log: log, + blobs: blobs, + db: db, + me: me, + cfg: cfg, + client: client, + p2p: host, + bitswap: bitswap, + providing: providing, + grpc: grpc.NewServer(), + quit: &clean, + ready: make(chan struct{}), + } + + rpc := &rpcMux{Node: n} + p2p.RegisterP2PServer(n.grpc, rpc) + + for _, extra := range extraServers { + if extraServer, ok := extra.(groups.WebsiteServer); ok { + groups.RegisterWebsiteServer(n.grpc, extraServer) + break + } } return n, nil @@ -328,6 +224,19 @@ func (n *Node) Client(ctx context.Context, pid peer.ID) (p2p.P2PClient, error) { return n.client.Dial(ctx, pid) } +// SiteClient opens a connection with a remote website. +func (n *Node) SiteClient(ctx context.Context, pid peer.ID) (WebsiteClient, error) { + if err := n.Connect(ctx, n.p2p.Peerstore().PeerInfo(pid)); err != nil { + return nil, err + } + + conn, err := n.client.dialPeer(ctx, pid) + if err != nil { + return nil, err + } + return groups.NewWebsiteClient(conn), nil +} + // ArePrivateIPsAllowed check if private IPs (local) are allowed to connect. func (n *Node) ArePrivateIPsAllowed() bool { return !n.cfg.NoPrivateIps @@ -397,12 +306,7 @@ func (n *Node) Start(ctx context.Context) (err error) { // Start Mintter protocol listener over libp2p. { g.Go(func() error { - select { - case <-n.registered: - return n.grpc.Serve(lis) - case <-ctx.Done(): - return nil - } + return n.grpc.Serve(lis) }) g.Go(func() error { @@ -565,7 +469,7 @@ func newLibp2p(cfg config.P2P, device crypto.PrivKey, pool *sqlitex.Pool) (*ipfs }), ) } - if !cfg.PublicReachability && !cfg.NoRelay { + if !cfg.ForceReachabilityPublic && !cfg.NoRelay { opts = append(opts, libp2p.ForceReachabilityPrivate()) } diff --git a/backend/mttnet/mttnet_test.go b/backend/mttnet/mttnet_test.go index 45f3d945e0..a6d4f08c63 100644 --- a/backend/mttnet/mttnet_test.go +++ b/backend/mttnet/mttnet_test.go @@ -19,7 +19,7 @@ import ( "go.uber.org/zap" ) -var _ p2p.P2PServer = (*Server)(nil) +var _ p2p.P2PServer = (*rpcMux)(nil) func TestAddrs(t *testing.T) { addrs := []string{ @@ -38,7 +38,7 @@ func TestAddrs(t *testing.T) { require.Equal(t, addrs, AddrInfoToStrings(info)) } -func makeTestPeer(t *testing.T, name string, siteCfg ...config.Site) (*Node, context.CancelFunc) { +func makeTestPeer(t *testing.T, name string) (*Node, context.CancelFunc) { u := coretest.NewTester(name) db := storage.MakeTestDB(t) @@ -67,7 +67,7 @@ func makeTestPeer(t *testing.T, name string, siteCfg ...config.Site) (*Node, con errc := make(chan error, 1) ctx, cancel := context.WithCancel(context.Background()) f := future.New[*Node]() - NewServer(ctx, config.Default().Site, f.ReadOnly, nil, nil) + require.NoError(t, f.Resolve(n)) go func() { diff --git a/backend/mttnet/payments.go b/backend/mttnet/payments.go index 587dd332fa..765d0bc3cf 100644 --- a/backend/mttnet/payments.go +++ b/backend/mttnet/payments.go @@ -2,7 +2,6 @@ package mttnet import ( "context" - "fmt" p2p "mintter/backend/genproto/p2p/v1alpha" "google.golang.org/grpc/codes" @@ -16,11 +15,8 @@ type Invoicer interface { } // RequestInvoice creates a local invoice. -func (srv *Server) RequestInvoice(ctx context.Context, in *p2p.RequestInvoiceRequest) (*p2p.RequestInvoiceResponse, error) { - n, ok := srv.Node.Get() - if !ok { - return nil, fmt.Errorf("Node not ready yet") - } +func (srv *rpcMux) RequestInvoice(ctx context.Context, in *p2p.RequestInvoiceRequest) (*p2p.RequestInvoiceResponse, error) { + n := srv.Node if n.invoicer == nil { return nil, status.Errorf(codes.Unimplemented, "method RequestInvoice not ready yet") } diff --git a/backend/mttnet/site.go b/backend/mttnet/site.go deleted file mode 100644 index ae58a1f06e..0000000000 --- a/backend/mttnet/site.go +++ /dev/null @@ -1,962 +0,0 @@ -// Package mttnet exposes the site functions to be exposed over p2p. -package mttnet - -import ( - "context" - "crypto/rand" - "encoding/base64" - "fmt" - "io" - "mintter/backend/core" - accounts "mintter/backend/daemon/api/accounts/v1alpha" - documents "mintter/backend/genproto/documents/v1alpha" - site "mintter/backend/genproto/documents/v1alpha" - "mintter/backend/hyper" - "mintter/backend/mttnet/sitesql" - "net/http" - "net/url" - "reflect" - "runtime" - "strings" - "time" - - "crawshaw.io/sqlite" - "crawshaw.io/sqlite/sqlitex" - "github.com/libp2p/go-libp2p/core/peer" - "go.uber.org/zap" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - rpcpeer "google.golang.org/grpc/peer" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/encoding/protojson" - emptypb "google.golang.org/protobuf/types/known/emptypb" - "google.golang.org/protobuf/types/known/timestamppb" -) - -type headerKey string - -const ( - // GRPCPort is the port where sites will externally expose the grpc interface. - GRPCPort = 56002 - // TargetSiteHostnameHeader is the headers bearing the remote site hostname to proxy calls to. - TargetSiteHostnameHeader = "x-mintter-site-hostname" - // GRPCOriginAcc is the key to pass the account id via context down to a proxied call - // In initial site add, the account is not in the database and it needs to proxy to call redeemtoken. - GRPCOriginAcc headerKey = "x-mintter-site-grpc-origin-acc" - // WellKnownPath is the path (to be completed with http(s)+domain) to call to get data from site. - WellKnownPath = "api/mintter-well-known" -) - -func GetSiteInfoHttp(SiteHostname string) (*documents.SiteDiscoveryConfig, error) { - requestURL := fmt.Sprintf("%s/%s", SiteHostname, WellKnownPath) - - req, err := http.NewRequest(http.MethodGet, requestURL, nil) - if err != nil { - return nil, fmt.Errorf("add site: could not create request to well-known site: %w ", err) - } - - res, err := http.DefaultClient.Do(req) - if err != nil { - return nil, fmt.Errorf("add site: could not contact to provided site [%s]: %w ", requestURL, err) - } - defer res.Body.Close() - if res.StatusCode < 200 || res.StatusCode > 299 { - return nil, fmt.Errorf("add site: site info url [%s] not working. Status code: %d", requestURL, res.StatusCode) - } - - data, err := io.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("failed to read json body: %w", err) - } - - var resp documents.SiteDiscoveryConfig - - if err := protojson.Unmarshal(data, &resp); err != nil { - return nil, fmt.Errorf("failed to unmarshal JSON body: %w", err) - } - return &resp, nil -} - -// CreateInviteToken creates a new invite token for registering a new member. -func (srv *Server) CreateInviteToken(ctx context.Context, in *site.CreateInviteTokenRequest) (*site.InviteToken, error) { - _, _, proxied, res, err := srv.checkPermissions(ctx, site.Member_OWNER, in) - if err != nil { - return nil, err - } - if proxied { - retValue, ok := res.(*site.InviteToken) - if !ok { - return nil, fmt.Errorf("format of proxied return value not recognized") - } - return retValue, nil - } - - if in.Role == site.Member_OWNER { - return nil, fmt.Errorf("cannot create owner token, please update the owner manually in site config") - } - - if in.Role == site.Member_ROLE_UNSPECIFIED { - return nil, status.Errorf(codes.InvalidArgument, "token role must be specified") - } - - newToken := newInviteToken() - - now := time.Now() - - var expireTime time.Time - if in.ExpireTime != nil { - inTime := in.ExpireTime.AsTime() - if inTime.Before(now) { - return nil, fmt.Errorf("expiration time must be in the future") - } - expireTime = inTime - } else { - expireTime = now.Add(srv.InviteTokenExpirationDelay) - } - - n, err := srv.Node.Await(ctx) - if err != nil { - return nil, fmt.Errorf("node is not ready yet: %w", err) - } - - conn, release, err := n.db.Conn(ctx) - if err != nil { - return nil, err - } - defer release() - - if err = sitesql.AddToken(conn, newToken, expireTime.Unix(), int64(in.Role)); err != nil { - return nil, err - } - - if err := sitesql.RemoveExpiredTokens(conn); err != nil { - return nil, err - } - - return &site.InviteToken{ - Token: newToken, - ExpireTime: timestamppb.New(expireTime), - }, nil -} - -// RedeemInviteToken redeems a previously created invite token to register a new member. -func (srv *Server) RedeemInviteToken(ctx context.Context, in *site.RedeemInviteTokenRequest) (*site.RedeemInviteTokenResponse, error) { - acc, _, proxied, res, err := srv.checkPermissions(ctx, site.Member_ROLE_UNSPECIFIED, in) - if err != nil { - return nil, err - } - if proxied { - retValue, ok := res.(*site.RedeemInviteTokenResponse) - if !ok { - return nil, fmt.Errorf("format of proxied return value not recognized") - } - return retValue, nil - } - - n, ok := srv.Node.Get() - if !ok { - return nil, fmt.Errorf("node not ready yet") - } - - if acc.String() == srv.owner.String() { - return &site.RedeemInviteTokenResponse{Role: site.Member_OWNER}, nil - } - - conn, release, err := n.db.Conn(ctx) - if err != nil { - return nil, err - } - defer release() - - var resp *site.RedeemInviteTokenResponse - if err := sqlitex.WithTx(conn, func() error { - // check if that account already a member - if in.Token == "" { - role, err := sitesql.GetMemberRole(conn, acc) - if err != nil { - return err - } - if role == 0 { - return fmt.Errorf("only site owner can add a site without a token") - } - - resp = &site.RedeemInviteTokenResponse{Role: role} - return nil - } - - tokenInfo, err := sitesql.GetToken(conn, in.Token) - if err != nil { - return err - } - - if tokenInfo.InviteTokensRole == 0 { - return status.Errorf(codes.NotFound, "unknown invite token") - } - - expireTime := time.Unix(tokenInfo.InviteTokensExpireTime, 0) - - if err = sitesql.RemoveToken(conn, in.Token); err != nil { - return fmt.Errorf("could not redeem the token %w", err) - } - - if expireTime.Before(time.Now()) { - return fmt.Errorf("expired token") - } - - if _, err = sitesql.AddMember(conn, acc, tokenInfo.InviteTokensRole); err != nil { - return fmt.Errorf("failed to add member: %w", err) - } - - resp = &site.RedeemInviteTokenResponse{Role: site.Member_Role(tokenInfo.InviteTokensRole)} - - return nil - }); err != nil { - return nil, err - } - - return resp, err -} - -// GetSiteInfo Gets public-facing site information. -func (srv *Server) GetSiteInfo(ctx context.Context, in *site.GetSiteInfoRequest) (*site.SiteInfo, error) { - _, _, proxied, res, err := srv.checkPermissions(ctx, site.Member_ROLE_UNSPECIFIED, in) - if err != nil { - return nil, err - } - if proxied { - retValue, ok := res.(*site.SiteInfo) - if !ok { - return nil, fmt.Errorf("Format of proxied return value not recognized") - } - return retValue, nil - } - n, ok := srv.Node.Get() - if !ok { - return nil, fmt.Errorf("Node not ready yet") - } - conn, cancel, err := n.db.Conn(ctx) - if err != nil { - return nil, fmt.Errorf("Cannot connect to internal db: %w", err) - } - defer cancel() - //make GetSiteTitle that returns "" when does not find the title tag - title, err := sitesql.GetSiteTitle(conn) - if err != nil { - return nil, fmt.Errorf("Could not get title") - } - //make GetSiteDescription that returns "" when does not find the description tag - description, err := sitesql.GetSiteDescription(conn) - if err != nil { - return nil, fmt.Errorf("Could not get title") - } - return &site.SiteInfo{ - Hostname: srv.hostname, - Title: title.KVValue, - Description: description.KVValue, - Owner: srv.owner.String(), - }, nil -} - -// UpdateSiteInfo updates public-facing site information. Doesn't support partial updates, hence all the fields must be provided. -func (srv *Server) UpdateSiteInfo(ctx context.Context, in *site.UpdateSiteInfoRequest) (*site.SiteInfo, error) { - _, _, proxied, res, err := srv.checkPermissions(ctx, site.Member_OWNER, in) - if err != nil { - return nil, err - } - if proxied { - retValue, ok := res.(*site.SiteInfo) - if !ok { - return nil, fmt.Errorf("Format of proxied return value not recognized") - } - return retValue, nil - } - n, ok := srv.Node.Get() - if !ok { - return nil, fmt.Errorf("Node not ready yet") - } - conn, cancel, err := n.db.Conn(ctx) - if err != nil { - return nil, fmt.Errorf("Cannot connect to internal db: %w", err) - } - defer cancel() - - ret := site.SiteInfo{Hostname: srv.hostname, - Owner: srv.owner.String(), - } - if in.Title != "" { - if err = sitesql.SetSiteTitle(conn, in.Title); err != nil { - return nil, fmt.Errorf("Could not set new title: %w", err) - } - ret.Title = in.Title - } - if in.Description != "" { - if err = sitesql.SetSiteDescription(conn, in.Description); err != nil { - return nil, fmt.Errorf("Could not set new description: %w", err) - } - ret.Description = in.Description - } - // Now update the profile accordingly - if err = srv.updateSiteBio(ctx, in.Title, in.Description); err != nil { - return nil, fmt.Errorf("Update Site Info: Could not update Site Bio accordingly: %w", err) - } - return &ret, nil -} - -// ListMembers lists registered members on the site. -func (srv *Server) ListMembers(ctx context.Context, in *site.ListMembersRequest) (*site.ListMembersResponse, error) { - _, _, proxied, res, err := srv.checkPermissions(ctx, site.Member_EDITOR, in) - if err != nil { - return nil, err - } - if proxied { - retValue, ok := res.(*site.ListMembersResponse) - if !ok { - return nil, fmt.Errorf("Format of proxied return value not recognized") - } - return retValue, nil - } - var members []*site.Member - n, ok := srv.Node.Get() - if !ok { - return nil, fmt.Errorf("Node not ready yet") - } - conn, cancel, err := n.db.Conn(ctx) - if err != nil { - return nil, fmt.Errorf("Cannot connect to internal db: %w", err) - } - defer cancel() - memberList, err := sitesql.ListMembers(conn) - if err != nil { - return nil, fmt.Errorf("Cannot get site members: %w", err) - } - for _, member := range memberList { - members = append(members, &site.Member{ - AccountId: core.Principal(member.PublicKeysPrincipal).String(), - Role: site.Member_Role(member.SiteMembersRole), - }) - } - return &site.ListMembersResponse{Members: members}, nil -} - -// GetMember gets information about a specific member. -func (srv *Server) GetMember(ctx context.Context, in *site.GetMemberRequest) (*site.Member, error) { - _, _, proxied, res, err := srv.checkPermissions(ctx, site.Member_EDITOR, in) - if err != nil { - return nil, err - } - if proxied { - retValue, ok := res.(*site.Member) - if !ok { - return nil, fmt.Errorf("format of proxied return value not recognized") - } - return retValue, nil - } - n, ok := srv.Node.Get() - if !ok { - return nil, fmt.Errorf("node not ready yet") - } - - account, err := core.DecodePrincipal(in.AccountId) - if err != nil { - return nil, fmt.Errorf("failed to decode account id principal %s: %w", in.AccountId, err) - } - - conn, cancel, err := n.db.Conn(ctx) - if err != nil { - return nil, fmt.Errorf("cannot connect to internal db: %w", err) - } - defer cancel() - - role, err := sitesql.GetMemberRole(conn, account) - if err != nil { - return nil, fmt.Errorf("member not found") - } - return &site.Member{AccountId: in.AccountId, Role: role}, nil -} - -// DeleteMember deletes an existing member. -func (srv *Server) DeleteMember(ctx context.Context, in *site.DeleteMemberRequest) (*emptypb.Empty, error) { - _, _, proxied, res, err := srv.checkPermissions(ctx, site.Member_OWNER, in) - if err != nil { - return nil, err - } - if proxied { - retValue, ok := res.(*emptypb.Empty) - if !ok { - return nil, fmt.Errorf("Format of proxied return value not recognized") - } - return retValue, nil - } - - n, ok := srv.Node.Get() - if !ok { - return nil, fmt.Errorf("Node not ready yet") - } - - account, err := core.DecodePrincipal(in.AccountId) - if err != nil { - return nil, fmt.Errorf("Provided account id [%s] not a valid cid: %w", in.AccountId, err) - } - - conn, cancel, err := n.db.Conn(ctx) - if err != nil { - return nil, fmt.Errorf("Cannot connect to internal db: %w", err) - } - defer cancel() - roleToDelete, err := sitesql.GetMemberRole(conn, account) - if err != nil { - return nil, fmt.Errorf("Member not found") - } - - if roleToDelete == site.Member_OWNER { - return nil, fmt.Errorf("Site owner cannot be deleted, please, change it manually in site config") - } - if err = sitesql.RemoveMember(conn, account); err != nil { - return nil, fmt.Errorf("Could not remove provided member [%s]: %w", in.AccountId, err) - } - - return &emptypb.Empty{}, nil -} - -// PublishDocument publishes and pins the document to the public web site. -func (srv *Server) PublishDocument(ctx context.Context, in *site.PublishDocumentRequest) (*site.PublishDocumentResponse, error) { - _, pid, proxied, res, err := srv.checkPermissions(ctx, site.Member_EDITOR, in) - if err != nil { - return nil, err - } - if proxied { - retValue, ok := res.(*site.PublishDocumentResponse) - if !ok { - return nil, fmt.Errorf("format of proxied return value not recognized") - } - return retValue, nil - } - - if in.DocumentId == "" { - return nil, status.Errorf(codes.InvalidArgument, "must specify document ID") - } - - u, err := url.Parse(in.Path) - if err != nil || u.Path != in.Path { - return nil, fmt.Errorf("path %s is not a valid path", in.Path) - } - - n, err := srv.Node.Await(ctx) - if err != nil { - return nil, fmt.Errorf("can't proxy: local p2p node is not ready yet: %w", err) - } - - docEntity := hyper.EntityID(in.DocumentId) - - toSync := []hyper.EntityID{docEntity} - - for _, ref := range in.ReferencedDocuments { - toSync = append(toSync, hyper.EntityID(ref.DocumentId)) - } - - ctx, cancel := context.WithTimeout(ctx, time.Duration(7*time.Second)) - defer cancel() - n.log.Debug("Publish Document: Syncing...", zap.String("DeviceID", pid.String()), zap.Int("Documents to sync", len(toSync))) - - if err = srv.synchronizer.SyncWithPeer(ctx, pid, toSync...); err != nil { - n.log.Debug("Publish Document: couldn't sync content with device", zap.String("device", pid.String()), zap.Error(err)) - return nil, fmt.Errorf("Publish Document: couldn't sync content with device: %w", err) - } - n.log.Debug("Successfully synced", zap.String("Peer", pid.String())) - - _, err = srv.localFunctions.GetPublication(ctx, &site.GetPublicationRequest{ - DocumentId: in.DocumentId, - Version: in.Version, - LocalOnly: true, - }) - if err != nil { - return nil, fmt.Errorf("couldn't find the actual document + version to publish in the database: %w", err) - } - - // If path already taken, we update in case doc_ids match (just updating the version) error otherwise - if err := n.db.WithTx(ctx, func(conn *sqlite.Conn) error { - record, err := sitesql.GetWebPublicationRecordByPath(conn, in.Path) - if err != nil { - return err - } - - if record.EntitiesID != 0 { - recordEntity := hyper.EntityID(record.EntitiesEID) - if !recordEntity.HasPrefix("hm://d/") { - return fmt.Errorf("invalid entity ID for mintter document: %s", record.EntitiesEID) - } - - if recordEntity == docEntity && record.WebPublicationsVersion == in.Version { - return fmt.Errorf("provided document+version already exists in path [%s]", in.Path) - } - if recordEntity != docEntity { - return fmt.Errorf("path %q is already taken by a different entity %q, can't use it for document %q", in.Path, recordEntity, in.DocumentId) - } - if err = sitesql.RemoveWebPublicationRecord(conn, record.EntitiesEID, record.WebPublicationsVersion); err != nil { - return fmt.Errorf("could not remove previous version [%s] in the same path: %w", record.WebPublicationsVersion, err) - } - } - - if err := sitesql.AddWebPublicationRecord(conn, string(docEntity), in.Version, in.Path); err != nil { - return fmt.Errorf("could not insert document in path [%s]: %w", in.Path, err) - } - - return nil - }); err != nil { - return nil, err - } - - return &site.PublishDocumentResponse{}, nil -} - -// UnpublishDocument un-publishes a given document. Only the author of that document or the owner can unpublish. -func (srv *Server) UnpublishDocument(ctx context.Context, in *site.UnpublishDocumentRequest) (*site.UnpublishDocumentResponse, error) { - acc, _, proxied, res, err := srv.checkPermissions(ctx, site.Member_EDITOR, in) - if err != nil { - return nil, err - } - if proxied { - retValue, ok := res.(*site.UnpublishDocumentResponse) - if !ok { - return nil, fmt.Errorf("Format of proxied return value not recognized") - } - return retValue, nil - } - n, ok := srv.Node.Get() - if !ok { - return nil, fmt.Errorf("Node not ready yet") - } - conn, cancel, err := n.db.Conn(ctx) - if err != nil { - return nil, fmt.Errorf("Cannot connect to internal db: %w", err) - } - defer cancel() - - eid := hyper.EntityID(in.DocumentId) - - records, err := sitesql.GetWebPublicationsByID(conn, string(eid)) - if err != nil { - return nil, fmt.Errorf("Cannot unpublish: %w", err) - } - for _, record := range records { - if in.Version == "" || in.Version == record.WebPublicationsVersion { - doc, err := srv.localFunctions.GetPublication(ctx, &site.GetPublicationRequest{ - DocumentId: in.DocumentId, - Version: record.WebPublicationsVersion, - LocalOnly: true, - }) - if err != nil { - return nil, fmt.Errorf("couldn't find the actual document to unpublish although it was found in the database: %w", err) - } - if acc.String() != doc.Document.Author && srv.owner.String() != acc.String() { - return nil, fmt.Errorf("you are not the author of the document, nor site owner") - } - if err = sitesql.RemoveWebPublicationRecord(conn, string(eid), record.WebPublicationsVersion); err != nil { - return nil, fmt.Errorf("couldn't remove document [%s]: %w", in.DocumentId, err) - } - } - } - return &site.UnpublishDocumentResponse{}, nil -} - -// ListWebPublications lists all the published documents. -func (srv *Server) ListWebPublications(ctx context.Context, in *site.ListWebPublicationsRequest) (*site.ListWebPublicationsResponse, error) { - _, _, proxied, res, err := srv.checkPermissions(ctx, site.Member_ROLE_UNSPECIFIED, in) - if err != nil { - return nil, err - } - if proxied { - retValue, ok := res.(*site.ListWebPublicationsResponse) - if !ok { - return nil, fmt.Errorf("Format of proxied return value not recognized") - } - return retValue, nil - } - var publications []*site.WebPublicationRecord - n, ok := srv.Node.Get() - if !ok { - return nil, fmt.Errorf("Node not ready yet") - } - conn, cancel, err := n.db.Conn(ctx) - if err != nil { - return nil, fmt.Errorf("Cannot connect to internal db: %w", err) - } - defer cancel() - records, err := sitesql.ListWebPublications(conn) - if err != nil { - return nil, fmt.Errorf("Cannot List publications: %w", err) - } - - for _, record := range records { - docid := hyper.EntityID(record.EntitiesEID) - if !docid.HasPrefix("hm://d/") { - return nil, fmt.Errorf("BUG: invalid entity ID %q for a document in web publications", record.EntitiesEID) - } - - if in.DocumentId != "" && in.DocumentId != string(docid) { - continue - } - - publications = append(publications, &site.WebPublicationRecord{ - DocumentId: string(docid), - Version: record.WebPublicationsVersion, - Hostname: srv.hostname, - Path: record.WebPublicationsPath, - }) - } - return &site.ListWebPublicationsResponse{Publications: publications}, nil -} - -// GetPath gets a publication given the path it has been publish to. -func (srv *Server) GetPath(ctx context.Context, in *site.GetPathRequest) (*site.GetPathResponse, error) { - _, _, proxied, res, err := srv.checkPermissions(ctx, site.Member_ROLE_UNSPECIFIED, in) - if err != nil { - return nil, err - } - if proxied { - retValue, ok := res.(*site.GetPathResponse) - if !ok { - return nil, fmt.Errorf("Format of proxied return value not recognized") - } - return retValue, nil - } - if in.Path == "" { - return nil, fmt.Errorf("Invalid path") - } - n, ok := srv.Node.Get() - if !ok { - return nil, fmt.Errorf("Node not ready yet") - } - conn, cancel, err := n.db.Conn(ctx) - if err != nil { - return nil, fmt.Errorf("Cannot connect to internal db: %w", err) - } - defer cancel() - record, err := sitesql.GetWebPublicationRecordByPath(conn, in.Path) - if err != nil { - return nil, fmt.Errorf("Could not get record for path [%s]: %w", in.Path, err) - } - ret, err := srv.localFunctions.GetPublication(ctx, &site.GetPublicationRequest{ - DocumentId: record.EntitiesEID, - Version: record.WebPublicationsVersion, - LocalOnly: true, - }) - if err != nil { - return nil, fmt.Errorf("Could not get local document although was found in the list of published documents: %w", err) - } - return &site.GetPathResponse{Publication: ret}, err -} - -func getRemoteSiteFromHeader(ctx context.Context) (string, error) { - md, ok := metadata.FromIncomingContext(ctx) - if !ok { - return "", fmt.Errorf("There is no metadata provided in context") - } - token := md.Get(string(TargetSiteHostnameHeader)) - if len(token) != 1 { - return "", fmt.Errorf("Header [%s] not found in metadata", TargetSiteHostnameHeader) - } - return token[0], nil -} - -func getGrpcOriginAcc(ctx context.Context) (string, error) { - md, ok := metadata.FromIncomingContext(ctx) - if !ok { - return "", fmt.Errorf("There is no metadata provided in context") - } - token := md.Get(string(GRPCOriginAcc)) - if len(token) != 1 { - return "", fmt.Errorf("Header [%s] not found in metadata", GRPCOriginAcc) - } - return token[0], nil -} - -// getRemoteID gets the remote peer id if there is an opened p2p connection between them with context ctx. -func getRemoteID(ctx context.Context) (peer.ID, error) { - info, ok := rpcpeer.FromContext(ctx) - if !ok { - return "", fmt.Errorf("BUG: no peer info in context for grpc") - } - - pid, err := peer.Decode(info.Addr.String()) - if err != nil { - return "", err - } - - return pid, nil -} - -func newInviteToken() string { - randomBytes := make([]byte, 16) - _, err := rand.Read(randomBytes) - if err != nil { - panic(err) - } - return base64.RawURLEncoding.EncodeToString(randomBytes) -} - -// Client dials a remote peer if necessary and returns the RPC client handle. -func (srv *Server) Client(ctx context.Context, remoteHostname string) (site.WebSiteClient, error) { - n, ok := srv.Node.Get() - if !ok { - return nil, fmt.Errorf("Node not ready yet") - } - - conn, cancel, err := n.db.Conn(ctx) - if err != nil { - return nil, err - } - defer cancel() - // actually we only need the peerID, not the addresses, but they are - // handy in case we are not connected so we don't call well-known. - remoteSite, err := sitesql.GetSite(conn, remoteHostname) - if err != nil { - return nil, fmt.Errorf("Could not get address info for site [%s]: %w", remoteHostname, err) - } - addrs := remoteSite.SitesAddresses - - if addrs == "" { //means we haven't added the site yet (redeem token at adding site). - n.log.Info("No site addresses yet, trying to get addresses from well-known") - resp, err := GetSiteInfoHttp(remoteHostname) - if err != nil { - return nil, fmt.Errorf("Could not get site [%s] info via http: %w", remoteHostname, err) - } - info, err := AddrInfoFromStrings(resp.Addresses...) - if err != nil { - return nil, fmt.Errorf("Couldn't parse multiaddress [%s]: %w", strings.Join(resp.Addresses, ","), err) - } - if err := n.Connect(ctx, info); err != nil { - return nil, fmt.Errorf("failed to connect to site [%s] with new addresses [%s]: %w", remoteHostname, strings.Join(resp.Addresses, ","), err) - } - if remoteSite.SitesHostname != "" && remoteSite.SitesAddresses != "" && remoteSite.SitesAddresses != strings.Join(resp.Addresses, ",") { // if "" means We haven't added the site yet (redeem token at adding site). - //update the site with new addresses - if err = sitesql.AddSite(conn, remoteSite.PublicKeysPrincipal, strings.Join(resp.Addresses, ","), remoteHostname, remoteSite.SitesRole); err != nil { - return nil, fmt.Errorf("add site: could not insert site in the database: %w", err) - } - } - return n.client.DialSite(ctx, info.ID) - } - info, err := AddrInfoFromStrings(strings.Split(addrs, ",")...) - - // we don't want to call well-known if either 1) we are already connected or 2) we were connected - // in previous sessions and we just woke up. In either case calling well-known would be slowdown. - // The only case where well-known will be called again is if the old addresses are no longer valid - // i.e. site owner deleted the db and started over with new peer IDs. - if err == nil { - err = n.Connect(ctx, info) - } - - if err != nil { - n.log.Warn("Failed to connect with old addresses, getting new addresses", zap.String("Peer Info", info.String()), zap.Error(err)) - resp, err := GetSiteInfoHttp(remoteHostname) - if err != nil { - return nil, fmt.Errorf("Could not get site [%s] info via http: %w", remoteHostname, err) - } - info, err := AddrInfoFromStrings(resp.Addresses...) - if err != nil { - return nil, fmt.Errorf("Couldn't parse multiaddress [%s]: %w", strings.Join(resp.Addresses, ","), err) - } - if err := n.Connect(ctx, info); err != nil { - return nil, fmt.Errorf("failed to connect to site [%s] with new addresses [%s]: %w", remoteHostname, strings.Join(resp.Addresses, ","), err) - } - if remoteSite.SitesHostname != "" && remoteSite.SitesAddresses != "" && remoteSite.SitesAddresses != strings.Join(resp.Addresses, ",") { // if "" means We haven't added the site yet (redeem token at adding site). - //update the site with new addresses - if err = sitesql.AddSite(conn, remoteSite.PublicKeysPrincipal, strings.Join(resp.Addresses, ","), remoteHostname, remoteSite.SitesRole); err != nil { - return nil, fmt.Errorf("add site: could not insert site in the database: %w", err) - } - } - } - - return n.client.DialSite(ctx, info.ID) -} - -func (srv *Server) checkPermissions(ctx context.Context, requiredRole site.Member_Role, params ...interface{}) (core.Principal, peer.ID, bool, interface{}, error) { - n, ok := srv.Node.Get() - if !ok { - return nil, "", false, nil, fmt.Errorf("Node not ready yet") - } - - remoteHostname, err := getRemoteSiteFromHeader(ctx) - if err != nil && srv.hostname == "" { // no headers and not a site - return nil, "", false, nil, fmt.Errorf("This node is not a site, please provide a proper headers to proxy the call to a remote site: %w", err) - } - - acc := n.me.Account().Principal() - peerID := n.me.Account().PeerID() - n.log.Debug("Check permissions", zap.String("Site hostname", srv.hostname), zap.String("remoteHostname", remoteHostname), zap.Error(err)) - if err == nil && srv.hostname != remoteHostname && srv.hostname != "" { - return acc, peerID, false, nil, fmt.Errorf("Hostnames don't match. This site's hostname is [%s] but called with headers [%s]", srv.hostname, remoteHostname) - } - - if err == nil && srv.hostname != remoteHostname && srv.hostname == "" { //This call is intended to be proxied so its site's duty to check permission - // proxy to remote - if len(params) == 0 { - n.log.Error("Headers found, meaning this call should be proxied, but remote function params not provided") - return acc, peerID, false, nil, fmt.Errorf("In order to proxy a call (headers found) you need to provide a valid proxy func and a params") - } - n.log.Debug("Headers found, meaning this call should be proxied and authentication will take place at the remote site", zap.String(string(TargetSiteHostnameHeader), remoteHostname)) - - // We will extract the caller's function name so we know which function to call in the remote site - // We opted to to make it generic so the proxying code is in one place only (proxyToSite). - pc, _, _, _ := runtime.Caller(1) - proxyFcnList := strings.Split(runtime.FuncForPC(pc).Name(), ".") - proxyFcn := proxyFcnList[len(proxyFcnList)-1] - // Since proxyFcn is taken from the name of the caller (same codebase as the one - // in proxyToSite), we don't expect the reflection to panic at runtime. - res, errInterface := srv.proxyToSite(ctx, remoteHostname, proxyFcn, params...) - if errInterface != nil { - err, ok := errInterface.(error) - if !ok { - return acc, peerID, true, res, fmt.Errorf("Proxied call returned unknown second parameter. Error type expected") - } - return acc, peerID, true, res, err - } - return acc, peerID, true, res, nil - } - - if err != nil || srv.hostname == remoteHostname { //either a proxied call or a direct call without headers (nodejs) - // this would mean this is a proxied call so we take the account from the remote caller ID - remoteAcc, err := getGrpcOriginAcc(ctx) - if err != nil { - remoteDeviceID, err := getRemoteID(ctx) - if err == nil { - // this would mean this is a proxied call so we take the account from the remote caller ID - remotAcc, err := n.AccountForDevice(ctx, remoteDeviceID) - if err != nil { - return nil, "", false, nil, fmt.Errorf("checkPermissions: couldn't get account ID from device [%s]: %w", remoteDeviceID.String(), err) - } - - n.log.Debug("PROXIED CALL", zap.String("Local AccountID", acc.String()), zap.String("Remote AccountID", remotAcc.String()), zap.Error(err)) - acc = remotAcc - peerID = remoteDeviceID - } else { - // this would mean we cannot get remote ID it must be a local call - n.log.Debug("LOCAL CALL", zap.String("Local AccountID", acc.String()), zap.String("remoteHostname", remoteHostname), zap.Error(err)) - } - } else { - acc, err = core.DecodePrincipal(remoteAcc) - if err != nil { - return nil, "", false, nil, fmt.Errorf("Invalid Account [%s]: %w", remoteAcc, err) - } - - } - - } - - if !srv.NoAuth && requiredRole == site.Member_OWNER && acc.String() != srv.owner.String() { - return nil, "", false, nil, fmt.Errorf("Unauthorized. Required role: %d", requiredRole) - } else if requiredRole == site.Member_EDITOR && acc.String() != srv.owner.String() { - conn, cancel, err := n.db.Conn(ctx) - if err != nil { - return nil, "", false, nil, fmt.Errorf("Cannot connect to internal db: %w", err) - } - defer cancel() - - role, err := sitesql.GetMemberRole(conn, acc) - if !srv.NoAuth && (err != nil || role != requiredRole) { - return nil, "", false, nil, fmt.Errorf("Unauthorized. Required role: %d", requiredRole) - } - } - return acc, peerID, false, nil, nil -} - -// updateSiteBio updates the site bio according to the site SEO description. -func (srv *Server) updateSiteBio(ctx context.Context, title, description string) error { - n, ok := srv.Node.Get() - if !ok { - return fmt.Errorf("Node not ready yet") - } - - return accounts.UpdateProfile(ctx, n.me, n.blobs, &accounts.Profile{ - Alias: title, - Bio: description, - }) -} - -// ServeHTTP serves the content for the well-known path. -func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") - w.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET") - if srv.hostname == "" { // if i'm not a site, then don't expose addresses - http.Error(w, "This node is not a Site", http.StatusExpectationFailed) - return - } - - info := &documents.SiteDiscoveryConfig{} - - n, ok := srv.Node.Get() - if !ok { - w.Header().Set("Retry-After", "30") - http.Error(w, "P2P node is not ready yet", http.StatusServiceUnavailable) - return - } - pid := n.me.DeviceKey().PeerID() - - info.AccountId = n.me.Account().String() - info.PeerId = pid.String() - - addrinfo := n.AddrInfo() - mas, err := peer.AddrInfoToP2pAddrs(&addrinfo) - if err != nil { - http.Error(w, "failed to get our own adresses", http.StatusInternalServerError) - return - } - - for _, addr := range mas { - info.Addresses = append(info.Addresses, addr.String()) - } - - data, err := protojson.MarshalOptions{ - Multiline: true, - Indent: " ", - }.Marshal(info) - if err != nil { - http.Error(w, "Failed to marshal site info: "+err.Error(), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write(data) -} - -// proxyToSite calls a remote site function over libp2p. It uses reflections to -// avoid having the proxying code spread in many function calls. Since the -// function's name to call is taken from this codebase, there should not be any -// inconsistency and no panic is expected at runtime (due to unknown function name). -func (srv *Server) proxyToSite(ctx context.Context, siteHostname string, proxyFcn string, params ...interface{}) (interface{}, interface{}) { - n, ok := srv.Node.Get() - if !ok { - return nil, fmt.Errorf("can't proxy. Local p2p node not ready yet") - } - n.log.Debug("Dialing remote peer....", zap.String("Hostname", siteHostname)) - sitec, err := srv.Client(ctx, siteHostname) - if err != nil { - return nil, fmt.Errorf("failed to dial to site: %w", err) - } - n.log.Debug("Remote site dialed, now try to call a remote function", zap.String("Function name", proxyFcn)) - - in := []reflect.Value{reflect.ValueOf(ctx)} - for _, param := range params { - in = append(in, reflect.ValueOf(param)) - } - in = append(in, reflect.ValueOf([]grpc.CallOption{})) - - f := reflect.ValueOf(sitec).MethodByName(proxyFcn) - if !f.IsValid() { - return nil, fmt.Errorf("Won't call %s since it does not exist", proxyFcn) - } - if f.Type().NumOut() != 2 { - return nil, fmt.Errorf("Proxied call %s expected to return 2 (return value + error) param but returns %d", proxyFcn, f.Type().NumOut()) - } - if len(params) != f.Type().NumIn()-2 { - return nil, fmt.Errorf("function %s needs %d params, %d provided", proxyFcn, f.Type().NumIn(), len(params)+2) - } - n.log.Debug("Calling Remote function...", zap.String("Function name", proxyFcn)) - res := f.CallSlice(in) - - n.log.Debug("Remote call finished", zap.String("method", proxyFcn), zap.Any("returnValues", [2]string{res[0].Type().String(), res[1].Type().String()}), zap.Any("error", res[1].Interface())) - return res[0].Interface(), res[1].Interface() -} diff --git a/backend/mttnet/site_test.go b/backend/mttnet/site_test.go deleted file mode 100644 index b579a9f2d2..0000000000 --- a/backend/mttnet/site_test.go +++ /dev/null @@ -1,353 +0,0 @@ -package mttnet - -import ( - "context" - "mintter/backend/config" - "mintter/backend/core/coretest" - daemon "mintter/backend/daemon/api/daemon/v1alpha" - "mintter/backend/daemon/storage" - documents "mintter/backend/genproto/documents/v1alpha" - siteproto "mintter/backend/genproto/documents/v1alpha" - "mintter/backend/hyper" - "mintter/backend/logging" - "mintter/backend/pkg/future" - "mintter/backend/pkg/must" - "net/http" - "testing" - "time" - - "github.com/stretchr/testify/require" - "go.uber.org/zap" - "google.golang.org/grpc/metadata" - "google.golang.org/protobuf/types/known/timestamppb" -) - -func TestLocalPublish(t *testing.T) { - t.Skip("not ready yet") - t.Parallel() - cfg := config.Default() - cfg.Site.Hostname = "example.com" - _, stopSite := makeTestPeer(t, "alice", cfg.Site) - defer stopSite() -} - -func TestMembers(t *testing.T) { - ownerSrv, docSrv, stopowner := makeTestSrv(t, "alice") - owner, ok := ownerSrv.Node.Get() - require.True(t, ok) - defer stopowner() - - editorSrv, _, stopeditor := makeTestSrv(t, "bob") - editor, ok := editorSrv.Node.Get() - require.True(t, ok) - defer stopeditor() - - readerSrv, _, stopreader := makeTestSrv(t, "david") - reader, ok := readerSrv.Node.Get() - require.True(t, ok) - defer stopreader() - - cfg := config.Default() - cfg.Site.Hostname = "http://127.0.0.1:55001" - cfg.Site.OwnerID = owner.me.Account().String() - cfg.Site.NoAuth = false - siteSrv, _, stopSite := makeTestSrv(t, "carol", cfg.Site) - site, ok := siteSrv.Node.Get() - require.True(t, ok) - defer stopSite() - - docSrv.SetSiteAccount(site.me.Account().String()) - - ctx := context.Background() - - s := &http.Server{ - Addr: ":55001", - Handler: siteSrv, - } - defer s.Shutdown(ctx) - go s.ListenAndServe() - require.NoError(t, owner.Connect(ctx, site.AddrInfo())) - header := metadata.New(map[string]string{string(TargetSiteHostnameHeader): cfg.Site.Hostname}) - ctx = metadata.NewIncomingContext(ctx, header) // Typically, the headers are written by the client in the outgoing context and server receives them in the incoming. But here we are writing the server directly - addresses := []string{} - for _, ma := range site.AddrInfo().Addrs { - addresses = append(addresses, ma.String()+"/p2p/"+site.p2p.ID().String()) - } - site.p2p.ID().String() - - res, err := ownerSrv.RedeemInviteToken(ctx, &siteproto.RedeemInviteTokenRequest{}) - require.NoError(t, err) - require.Equal(t, documents.Member_OWNER, res.Role) - token, err := ownerSrv.CreateInviteToken(ctx, &documents.CreateInviteTokenRequest{ - Role: documents.Member_EDITOR, - ExpireTime: ×tamppb.Timestamp{Seconds: time.Now().Add(10 * time.Minute).Unix()}, - }) - require.NoError(t, err) - - require.NoError(t, reader.Connect(ctx, site.AddrInfo())) - _, err = readerSrv.RedeemInviteToken(ctx, &siteproto.RedeemInviteTokenRequest{}) - require.Error(t, err) - - require.NoError(t, editor.Connect(ctx, site.AddrInfo())) - res, err = editorSrv.RedeemInviteToken(ctx, &siteproto.RedeemInviteTokenRequest{Token: token.Token}) - require.NoError(t, err) - require.Equal(t, documents.Member_EDITOR, res.Role) - - _, err = editorSrv.GetMember(ctx, &siteproto.GetMemberRequest{AccountId: site.me.Account().String()}) - require.Error(t, err) - - member, err := editorSrv.GetMember(ctx, &siteproto.GetMemberRequest{AccountId: editor.me.Account().String()}) - require.NoError(t, err) - require.Equal(t, editor.me.Account().String(), member.AccountId) - require.Equal(t, documents.Member_EDITOR, member.Role) - - memberList, err := editorSrv.ListMembers(ctx, &siteproto.ListMembersRequest{}) - require.NoError(t, err) - require.Len(t, memberList.Members, 2) - - _, err = editorSrv.DeleteMember(ctx, &siteproto.DeleteMemberRequest{AccountId: editor.me.Account().String()}) - require.Error(t, err) - - _, err = ownerSrv.DeleteMember(ctx, &siteproto.DeleteMemberRequest{AccountId: editor.me.Account().String()}) - require.NoError(t, err) - - _, err = editorSrv.ListMembers(ctx, &siteproto.ListMembersRequest{}) - require.Error(t, err) - - memberList, err = ownerSrv.ListMembers(ctx, &siteproto.ListMembersRequest{}) - require.NoError(t, err) - require.Len(t, memberList.Members, 1) - require.Equal(t, documents.Member_OWNER, memberList.Members[0].Role) - require.Equal(t, owner.me.Account().String(), memberList.Members[0].AccountId) -} - -func TestCreateTokens(t *testing.T) { - ownerSrv, docSrv, stopowner := makeTestSrv(t, "alice") - owner, ok := ownerSrv.Node.Get() - require.True(t, ok) - defer stopowner() - - owner2Srv, _, stopowner2 := makeTestSrv(t, "alice-2") - require.True(t, ok) - defer stopowner2() - - editorSrv, _, stopeditor := makeTestSrv(t, "bob") - - require.True(t, ok) - defer stopeditor() - - cfg := config.Default() - cfg.Site.Hostname = "http://127.0.0.1:55001" - - cfg.Site.OwnerID = owner.me.Account().String() - siteSrv, _, stopSite := makeTestSrv(t, "carol", cfg.Site) - site, ok := siteSrv.Node.Get() - require.True(t, ok) - defer stopSite() - - docSrv.SetSiteAccount(site.me.Account().String()) - - ctx := context.Background() - - s := &http.Server{ - Addr: ":55001", - Handler: siteSrv, - } - defer s.Shutdown(ctx) - go s.ListenAndServe() - tsFuture := time.Now().Add(48 * time.Hour).Unix() - _, err := ownerSrv.CreateInviteToken(ctx, &documents.CreateInviteTokenRequest{ - Role: documents.Member_EDITOR, - ExpireTime: ×tamppb.Timestamp{Seconds: tsFuture}, - }) - require.Error(t, err) - - _, err = siteSrv.CreateInviteToken(ctx, &documents.CreateInviteTokenRequest{ - Role: documents.Member_EDITOR, - ExpireTime: ×tamppb.Timestamp{Seconds: tsFuture}, - }) - require.Error(t, err) - require.NoError(t, owner.Connect(ctx, site.AddrInfo())) - header := metadata.New(map[string]string{string(TargetSiteHostnameHeader): cfg.Site.Hostname}) - ctx = metadata.NewIncomingContext(ctx, header) // Typically, the headers are written by the client in the outgoing context and server receives them in the incoming. But here we are writing the server directly - addresses := []string{} - for _, ma := range site.AddrInfo().Addrs { - addresses = append(addresses, ma.String()+"/p2p/"+site.p2p.ID().String()) - } - - token, err := ownerSrv.CreateInviteToken(ctx, &documents.CreateInviteTokenRequest{ - Role: documents.Member_EDITOR, - ExpireTime: ×tamppb.Timestamp{Seconds: tsFuture}, - }) - require.NoError(t, err) - require.Equal(t, tsFuture, token.ExpireTime.Seconds) - - tsPast := time.Now().Add(-48 * time.Hour).Unix() - _, err = ownerSrv.CreateInviteToken(ctx, &documents.CreateInviteTokenRequest{ - Role: documents.Member_EDITOR, - ExpireTime: ×tamppb.Timestamp{Seconds: tsPast}, - }) - require.Error(t, err) - - _, err = ownerSrv.CreateInviteToken(ctx, &documents.CreateInviteTokenRequest{ - Role: documents.Member_OWNER, - ExpireTime: ×tamppb.Timestamp{Seconds: tsFuture}, - }) - require.Error(t, err) - - _, err = ownerSrv.CreateInviteToken(ctx, &documents.CreateInviteTokenRequest{ - Role: documents.Member_ROLE_UNSPECIFIED, - ExpireTime: ×tamppb.Timestamp{Seconds: tsFuture}, - }) - require.Error(t, err) - - _, err = editorSrv.CreateInviteToken(ctx, &documents.CreateInviteTokenRequest{ - Role: documents.Member_EDITOR, - ExpireTime: ×tamppb.Timestamp{Seconds: tsFuture}, - }) - - require.Error(t, err) - - _, err = owner2Srv.CreateInviteToken(ctx, &documents.CreateInviteTokenRequest{ - Role: documents.Member_EDITOR, - ExpireTime: ×tamppb.Timestamp{Seconds: tsFuture}, - }) - - require.NoError(t, err) -} - -func TestSiteInfo(t *testing.T) { - ownerSrv, docSrv, stopowner := makeTestSrv(t, "alice") - owner, ok := ownerSrv.Node.Get() - require.True(t, ok) - defer stopowner() - - cfg := config.Default() - cfg.Site.Hostname = "http://127.0.0.1:55001" - cfg.Site.Title = "My title" - cfg.Site.OwnerID = owner.me.Account().String() - siteSrv, _, stopSite := makeTestSrv(t, "bob", cfg.Site) - site, ok := siteSrv.Node.Get() - require.True(t, ok) - defer stopSite() - - docSrv.SetSiteAccount(site.me.Account().String()) - - ctx := context.Background() - - s := &http.Server{ - Addr: ":55001", - Handler: siteSrv, - } - defer s.Shutdown(ctx) - go s.ListenAndServe() - time.Sleep(500 * time.Millisecond) - require.NoError(t, owner.Connect(ctx, site.AddrInfo())) - header := metadata.New(map[string]string{string(TargetSiteHostnameHeader): cfg.Site.Hostname}) - ctx = metadata.NewIncomingContext(ctx, header) // Typically, the headers are written by the client in the outgoing context and server receives them in the incoming. But here we are writing the server directly - addresses := []string{} - for _, ma := range site.AddrInfo().Addrs { - addresses = append(addresses, ma.String()+"/p2p/"+site.p2p.ID().String()) - } - - res, err := ownerSrv.RedeemInviteToken(ctx, &siteproto.RedeemInviteTokenRequest{}) - require.NoError(t, err) - require.Equal(t, documents.Member_OWNER, res.Role) - - siteInfo, err := ownerSrv.GetSiteInfo(ctx, &documents.GetSiteInfoRequest{}) - require.NoError(t, err) - require.Equal(t, cfg.Site.Hostname, siteInfo.Hostname) - require.Equal(t, cfg.Site.OwnerID, siteInfo.Owner) - require.Equal(t, cfg.Site.Title, siteInfo.Title) - - const newTitle = " new title" - const newDescription = "new description" - siteInfo, err = ownerSrv.UpdateSiteInfo(ctx, &siteproto.UpdateSiteInfoRequest{ - Title: newTitle, - Description: newDescription, - }) - require.NoError(t, err) - require.Equal(t, newDescription, siteInfo.Description) - require.Equal(t, newTitle, siteInfo.Title) - require.Equal(t, cfg.Site.Hostname, siteInfo.Hostname) - require.Equal(t, owner.me.Account().String(), siteInfo.Owner) - siteInfo, err = ownerSrv.GetSiteInfo(ctx, &documents.GetSiteInfoRequest{}) - require.NoError(t, err) - require.Equal(t, newDescription, siteInfo.Description) - require.Equal(t, newTitle, siteInfo.Title) - require.Equal(t, cfg.Site.Hostname, siteInfo.Hostname) - require.Equal(t, owner.me.Account().String(), siteInfo.Owner) -} - -func makeTestSrv(t *testing.T, name string, siteCfg ...config.Site) (*Server, *simulatedDocs, context.CancelFunc) { - u := coretest.NewTester(name) - - db := storage.MakeTestDB(t) - - blobs := hyper.NewStorage(db, logging.New("mintter/hyper", "debug")) - _, err := daemon.Register(context.Background(), blobs, u.Account, u.Device.PublicKey, time.Now()) - require.NoError(t, err) - - cfg := config.Default() - - require.LessOrEqual(t, len(siteCfg), 1) - if len(siteCfg) == 1 { - cfg.Site = siteCfg[0] - } - - cfg.P2P.Port = 0 - cfg.P2P.BootstrapPeers = nil - cfg.P2P.NoRelay = true - cfg.P2P.NoMetrics = true - cfg.GRPCPort = GRPCPort - n, err := New(cfg.P2P, db, blobs, u.Identity, must.Do2(zap.NewDevelopment()).Named(name)) - require.NoError(t, err) - - errc := make(chan error, 1) - ctx, cancel := context.WithCancel(context.Background()) - f := future.New[*Node]() - docsSrv := newSimulatedDocs(&siteproto.Publication{}, "") - - srv := NewServer(ctx, cfg.Site, f.ReadOnly, docsSrv, nil) - require.NoError(t, f.Resolve(n)) - - go func() { - errc <- n.Start(ctx) - }() - - t.Cleanup(func() { - require.NoError(t, <-errc) - }) - - select { - case <-n.Ready(): - case err := <-errc: - require.NoError(t, err) - } - - return srv, docsSrv, cancel -} - -type simulatedDocs struct { - siteAccount string - publication *siteproto.Publication -} - -func newSimulatedDocs(pub *siteproto.Publication, siteAccount string) *simulatedDocs { - return &simulatedDocs{ - siteAccount: siteAccount, - publication: pub, - } -} - -func (s *simulatedDocs) SetSiteAccount(acc string) { - s.siteAccount = acc -} - -func (s *simulatedDocs) GetPublication(ctx context.Context, in *siteproto.GetPublicationRequest) (*siteproto.Publication, error) { - return s.publication, nil -} - -func (s *simulatedDocs) GetSiteAccount(hostname string) (string, error) { - return s.siteAccount, nil -} diff --git a/backend/mttnet/sitesV2.go b/backend/mttnet/sitesV2.go deleted file mode 100644 index 29b6e14236..0000000000 --- a/backend/mttnet/sitesV2.go +++ /dev/null @@ -1,99 +0,0 @@ -// Package mttnet exposes the site functions to be exposed over p2p. -package mttnet - -import ( - "context" - "fmt" - "mintter/backend/core" - sitesV2 "mintter/backend/genproto/p2p/v1alpha" - "mintter/backend/hyper/hypersql" - "mintter/backend/mttnet/sitesql" - "net/http" - "strings" - - "github.com/libp2p/go-libp2p/core/peer" -) - -const ( - // OwnerAccountHeader is the key to get the account id of the site's owner out of http response headers. - OwnerAccountHeader = "x-mintter-site-owner-account" - // GroupIDHeader is the key to get the group id of the group served on the site. - GroupIDHeader = "x-mintter-site-group-id" - // P2PAddresses is the key to get the p2p addresses to connect to that site via p2p. - P2PAddresses = "x-mintter-site-p2p-addresses" -) - -// CreateSite creates a site from a group. -func (srv *Server) CreateSite(ctx context.Context, in *sitesV2.CreateSiteRequest) (*sitesV2.CreateSiteResponse, error) { - n, err := srv.Node.Await(ctx) - if err != nil { - return nil, fmt.Errorf("node is not ready yet: %w", err) - } - remoteDeviceID, err := getRemoteID(ctx) - - var remoteAcc core.Principal - if err != nil { - return nil, fmt.Errorf("Only remote calls accepted: %w", err) - } - - remoteAcc, err = n.AccountForDevice(ctx, remoteDeviceID) - if err != nil { - return nil, fmt.Errorf("couldn't get account ID from device [%s]: %w", remoteDeviceID.String(), err) - } - - conn, release, err := n.db.Conn(ctx) - if err != nil { - return nil, err - } - defer release() - - link, err := sitesql.GetSiteRegistrationLink(conn) - if err != nil { - return nil, err - } - - if link.KVValue != in.Link { - return nil, fmt.Errorf("Provided link not valid") - } - - hostname := strings.Split(in.Link, "/secret-invite/")[0] - - _, err = hypersql.EntitiesInsertOrIgnore(conn, in.GroupId) - if err != nil { - return nil, err - } - - if err := sitesql.RegisterSite(conn, hostname, in.GroupId, in.Version, remoteAcc); err != nil { - return nil, err - } - - return &sitesV2.CreateSiteResponse{ - OwnerId: remoteAcc.String(), - GroupId: in.GroupId, - }, nil -} - -// GetSiteAddressFromHeaders gets peer information from site http response headers. -func GetSiteAddressFromHeaders(SiteHostname string) (peer.AddrInfo, error) { - var resp peer.AddrInfo - req, err := http.NewRequest(http.MethodGet, SiteHostname, nil) - if err != nil { - return resp, fmt.Errorf("could not create request to [%s] site: %w ", SiteHostname, err) - } - - res, err := http.DefaultClient.Do(req) - if err != nil { - return resp, fmt.Errorf("could not contact provided site [%s]: %w ", SiteHostname, err) - } - defer res.Body.Close() - - addresses := res.Header.Get(P2PAddresses) - if addresses == "" { - return resp, fmt.Errorf("headers [%s] not present in http response", P2PAddresses) - } - resp, err = AddrInfoFromStrings(strings.Split(strings.Replace(addresses, " ", "", -1), ",")...) - if err != nil { - return resp, fmt.Errorf("Couldn't parse multiaddress [%s]: %w", addresses, err) - } - return resp, nil -} diff --git a/backend/mttnet/sitesql/queries.gen.go b/backend/mttnet/sitesql/queries.gen.go deleted file mode 100644 index 350540b1df..0000000000 --- a/backend/mttnet/sitesql/queries.gen.go +++ /dev/null @@ -1,699 +0,0 @@ -// Code generated by sqlitegen. DO NOT EDIT. - -package sitesql - -import ( - "errors" - "fmt" - - "crawshaw.io/sqlite" - "mintter/backend/pkg/sqlitegen" -) - -var _ = errors.New - -func RegisterSite(conn *sqlite.Conn, servedSitesHostname string, group_eid string, servedSitesVersion string, publicKeysPrincipal []byte) error { - const query = `INSERT OR REPLACE INTO served_sites (hostname, group_id, version, owner_id) -VALUES (:servedSitesHostname, (SELECT entities.id FROM entities WHERE entities.eid = :group_eid), :servedSitesVersion, (SELECT public_keys.id FROM public_keys WHERE public_keys.principal = :publicKeysPrincipal))` - - before := func(stmt *sqlite.Stmt) { - stmt.SetText(":servedSitesHostname", servedSitesHostname) - stmt.SetText(":group_eid", group_eid) - stmt.SetText(":servedSitesVersion", servedSitesVersion) - stmt.SetBytes(":publicKeysPrincipal", publicKeysPrincipal) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: RegisterSite: %w", err) - } - - return err -} - -type GetSiteInfoResult struct { - EntitiesEID string - ServedSitesVersion string - PublicKeysPrincipal []byte -} - -func GetSiteInfo(conn *sqlite.Conn, servedSitesHostname string) (GetSiteInfoResult, error) { - const query = `SELECT entities.eid, served_sites.version, public_keys.principal -FROM served_sites -JOIN entities ON entities.id = served_sites.group_id -JOIN public_keys ON public_keys.principal = served_sites.owner_id -WHERE served_sites.hostname = :servedSitesHostname` - - var out GetSiteInfoResult - - before := func(stmt *sqlite.Stmt) { - stmt.SetText(":servedSitesHostname", servedSitesHostname) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - if i > 1 { - return errors.New("GetSiteInfo: more than one result return for a single-kind query") - } - - out.EntitiesEID = stmt.ColumnText(0) - out.ServedSitesVersion = stmt.ColumnText(1) - out.PublicKeysPrincipal = stmt.ColumnBytes(2) - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: GetSiteInfo: %w", err) - } - - return out, err -} - -func AddSite(conn *sqlite.Conn, publicKeysPrincipal []byte, sitesAddresses string, sitesHostname string, sitesRole int64) error { - const query = `INSERT OR REPLACE INTO sites (account_id, addresses, hostname, role) -VALUES ((SELECT public_keys.id FROM public_keys WHERE public_keys.principal = :publicKeysPrincipal), :sitesAddresses, :sitesHostname, :sitesRole)` - - before := func(stmt *sqlite.Stmt) { - stmt.SetBytes(":publicKeysPrincipal", publicKeysPrincipal) - stmt.SetText(":sitesAddresses", sitesAddresses) - stmt.SetText(":sitesHostname", sitesHostname) - stmt.SetInt64(":sitesRole", sitesRole) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: AddSite: %w", err) - } - - return err -} - -func RemoveSite(conn *sqlite.Conn, sitesHostname string) error { - const query = `DELETE FROM sites WHERE sites.hostname = :sitesHostname` - - before := func(stmt *sqlite.Stmt) { - stmt.SetText(":sitesHostname", sitesHostname) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: RemoveSite: %w", err) - } - - return err -} - -type GetSiteResult struct { - SitesAddresses string - SitesHostname string - SitesRole int64 - PublicKeysPrincipal []byte -} - -func GetSite(conn *sqlite.Conn, sitesHostname string) (GetSiteResult, error) { - const query = `SELECT sites.addresses, sites.hostname, sites.role, public_keys.principal -FROM sites -JOIN public_keys ON public_keys.id = sites.account_id -WHERE sites.hostname = :sitesHostname` - - var out GetSiteResult - - before := func(stmt *sqlite.Stmt) { - stmt.SetText(":sitesHostname", sitesHostname) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - if i > 1 { - return errors.New("GetSite: more than one result return for a single-kind query") - } - - out.SitesAddresses = stmt.ColumnText(0) - out.SitesHostname = stmt.ColumnText(1) - out.SitesRole = stmt.ColumnInt64(2) - out.PublicKeysPrincipal = stmt.ColumnBytes(3) - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: GetSite: %w", err) - } - - return out, err -} - -type ListSitesResult struct { - SitesAddresses string - SitesHostname string - SitesRole int64 - PublicKeysPrincipal []byte -} - -func ListSites(conn *sqlite.Conn) ([]ListSitesResult, error) { - const query = `SELECT sites.addresses, sites.hostname, sites.role, public_keys.principal -FROM sites -JOIN public_keys ON public_keys.id = sites.account_id` - - var out []ListSitesResult - - before := func(stmt *sqlite.Stmt) { - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - out = append(out, ListSitesResult{ - SitesAddresses: stmt.ColumnText(0), - SitesHostname: stmt.ColumnText(1), - SitesRole: stmt.ColumnInt64(2), - PublicKeysPrincipal: stmt.ColumnBytes(3), - }) - - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: ListSites: %w", err) - } - - return out, err -} - -func SetSiteRegistrationLink(conn *sqlite.Conn, link string) error { - const query = `INSERT OR REPLACE INTO kv (key, value) -VALUES ('site_registration_link', :link)` - - before := func(stmt *sqlite.Stmt) { - stmt.SetText(":link", link) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: SetSiteRegistrationLink: %w", err) - } - - return err -} - -type GetSiteRegistrationLinkResult struct { - KVValue string -} - -func GetSiteRegistrationLink(conn *sqlite.Conn) (GetSiteRegistrationLinkResult, error) { - const query = `SELECT kv.value FROM kv WHERE kv.key ='site_registration_link'` - - var out GetSiteRegistrationLinkResult - - before := func(stmt *sqlite.Stmt) { - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - if i > 1 { - return errors.New("GetSiteRegistrationLink: more than one result return for a single-kind query") - } - - out.KVValue = stmt.ColumnText(0) - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: GetSiteRegistrationLink: %w", err) - } - - return out, err -} - -func SetSiteTitle(conn *sqlite.Conn, title string) error { - const query = `INSERT OR REPLACE INTO kv (key, value) -VALUES ('site_title', :title)` - - before := func(stmt *sqlite.Stmt) { - stmt.SetText(":title", title) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: SetSiteTitle: %w", err) - } - - return err -} - -type GetSiteTitleResult struct { - KVValue string -} - -func GetSiteTitle(conn *sqlite.Conn) (GetSiteTitleResult, error) { - const query = `SELECT kv.value FROM kv WHERE kv.key ='site_title'` - - var out GetSiteTitleResult - - before := func(stmt *sqlite.Stmt) { - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - if i > 1 { - return errors.New("GetSiteTitle: more than one result return for a single-kind query") - } - - out.KVValue = stmt.ColumnText(0) - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: GetSiteTitle: %w", err) - } - - return out, err -} - -func SetSiteDescription(conn *sqlite.Conn, description string) error { - const query = `INSERT OR REPLACE INTO kv (key, value) -VALUES ('site_description', :description)` - - before := func(stmt *sqlite.Stmt) { - stmt.SetText(":description", description) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: SetSiteDescription: %w", err) - } - - return err -} - -type GetSiteDescriptionResult struct { - KVValue string -} - -func GetSiteDescription(conn *sqlite.Conn) (GetSiteDescriptionResult, error) { - const query = `SELECT kv.value FROM kv WHERE kv.key ='site_description'` - - var out GetSiteDescriptionResult - - before := func(stmt *sqlite.Stmt) { - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - if i > 1 { - return errors.New("GetSiteDescription: more than one result return for a single-kind query") - } - - out.KVValue = stmt.ColumnText(0) - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: GetSiteDescription: %w", err) - } - - return out, err -} - -func AddToken(conn *sqlite.Conn, inviteTokensToken string, inviteTokensExpireTime int64, inviteTokensRole int64) error { - const query = `INSERT INTO invite_tokens (token, expire_time, role) -VALUES (:inviteTokensToken, :inviteTokensExpireTime, :inviteTokensRole)` - - before := func(stmt *sqlite.Stmt) { - stmt.SetText(":inviteTokensToken", inviteTokensToken) - stmt.SetInt64(":inviteTokensExpireTime", inviteTokensExpireTime) - stmt.SetInt64(":inviteTokensRole", inviteTokensRole) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: AddToken: %w", err) - } - - return err -} - -type GetTokenResult struct { - InviteTokensRole int64 - InviteTokensExpireTime int64 -} - -func GetToken(conn *sqlite.Conn, inviteTokensToken string) (GetTokenResult, error) { - const query = `SELECT invite_tokens.role, invite_tokens.expire_time -FROM invite_tokens WHERE invite_tokens.token = :inviteTokensToken` - - var out GetTokenResult - - before := func(stmt *sqlite.Stmt) { - stmt.SetText(":inviteTokensToken", inviteTokensToken) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - if i > 1 { - return errors.New("GetToken: more than one result return for a single-kind query") - } - - out.InviteTokensRole = stmt.ColumnInt64(0) - out.InviteTokensExpireTime = stmt.ColumnInt64(1) - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: GetToken: %w", err) - } - - return out, err -} - -func RemoveToken(conn *sqlite.Conn, inviteTokensToken string) error { - const query = `DELETE FROM invite_tokens WHERE invite_tokens.token = :inviteTokensToken` - - before := func(stmt *sqlite.Stmt) { - stmt.SetText(":inviteTokensToken", inviteTokensToken) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: RemoveToken: %w", err) - } - - return err -} - -func RemoveExpiredTokens(conn *sqlite.Conn) error { - const query = `DELETE FROM invite_tokens WHERE invite_tokens.expire_time < strftime('%s', 'now')` - - before := func(stmt *sqlite.Stmt) { - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: RemoveExpiredTokens: %w", err) - } - - return err -} - -type InsertMemberResult struct { - SiteMembersRole int64 -} - -func InsertMember(conn *sqlite.Conn, siteMembersAccountID int64, siteMembersRole int64) (InsertMemberResult, error) { - const query = `INSERT OR REPLACE INTO site_members (account_id, role) -VALUES (:siteMembersAccountID, :siteMembersRole) -RETURNING site_members.role` - - var out InsertMemberResult - - before := func(stmt *sqlite.Stmt) { - stmt.SetInt64(":siteMembersAccountID", siteMembersAccountID) - stmt.SetInt64(":siteMembersRole", siteMembersRole) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - if i > 1 { - return errors.New("InsertMember: more than one result return for a single-kind query") - } - - out.SiteMembersRole = stmt.ColumnInt64(0) - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: InsertMember: %w", err) - } - - return out, err -} - -func RemoveMember(conn *sqlite.Conn, publicKeysPrincipal []byte) error { - const query = `DELETE FROM site_members WHERE site_members.account_id = (SELECT public_keys.id FROM public_keys WHERE public_keys.principal = :publicKeysPrincipal)` - - before := func(stmt *sqlite.Stmt) { - stmt.SetBytes(":publicKeysPrincipal", publicKeysPrincipal) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: RemoveMember: %w", err) - } - - return err -} - -type GetMemberResult struct { - SiteMembersRole int64 -} - -func GetMember(conn *sqlite.Conn, publicKeysPrincipal []byte) (GetMemberResult, error) { - const query = `SELECT site_members.role -FROM site_members -WHERE site_members.account_id = (SELECT public_keys.id FROM public_keys WHERE public_keys.principal = :publicKeysPrincipal)` - - var out GetMemberResult - - before := func(stmt *sqlite.Stmt) { - stmt.SetBytes(":publicKeysPrincipal", publicKeysPrincipal) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - if i > 1 { - return errors.New("GetMember: more than one result return for a single-kind query") - } - - out.SiteMembersRole = stmt.ColumnInt64(0) - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: GetMember: %w", err) - } - - return out, err -} - -type ListMembersResult struct { - SiteMembersRole int64 - PublicKeysPrincipal []byte -} - -func ListMembers(conn *sqlite.Conn) ([]ListMembersResult, error) { - const query = `SELECT site_members.role, public_keys.principal -FROM site_members -JOIN public_keys ON public_keys.id = site_members.account_id` - - var out []ListMembersResult - - before := func(stmt *sqlite.Stmt) { - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - out = append(out, ListMembersResult{ - SiteMembersRole: stmt.ColumnInt64(0), - PublicKeysPrincipal: stmt.ColumnBytes(1), - }) - - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: ListMembers: %w", err) - } - - return out, err -} - -func InsertWebPublicationRecord(conn *sqlite.Conn, webPublicationsEID string, webPublicationsVersion string, webPublicationsPath string) error { - const query = `INSERT INTO web_publications (eid, version, path) -VALUES (:webPublicationsEID, :webPublicationsVersion, :webPublicationsPath)` - - before := func(stmt *sqlite.Stmt) { - stmt.SetText(":webPublicationsEID", webPublicationsEID) - stmt.SetText(":webPublicationsVersion", webPublicationsVersion) - stmt.SetText(":webPublicationsPath", webPublicationsPath) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: InsertWebPublicationRecord: %w", err) - } - - return err -} - -func RemoveWebPublicationRecord(conn *sqlite.Conn, entitiesEID string, webPublicationsVersion string) error { - const query = `DELETE FROM web_publications WHERE web_publications.eid = :entitiesEID AND web_publications.version = :webPublicationsVersion` - - before := func(stmt *sqlite.Stmt) { - stmt.SetText(":entitiesEID", entitiesEID) - stmt.SetText(":webPublicationsVersion", webPublicationsVersion) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: RemoveWebPublicationRecord: %w", err) - } - - return err -} - -type ListWebPublicationsResult struct { - EntitiesID int64 - EntitiesEID string - WebPublicationsVersion string - WebPublicationsPath string -} - -func ListWebPublications(conn *sqlite.Conn) ([]ListWebPublicationsResult, error) { - const query = `SELECT entities.id, entities.eid, web_publications.version, web_publications.path -FROM web_publications -JOIN entities ON web_publications.eid = entities.eid` - - var out []ListWebPublicationsResult - - before := func(stmt *sqlite.Stmt) { - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - out = append(out, ListWebPublicationsResult{ - EntitiesID: stmt.ColumnInt64(0), - EntitiesEID: stmt.ColumnText(1), - WebPublicationsVersion: stmt.ColumnText(2), - WebPublicationsPath: stmt.ColumnText(3), - }) - - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: ListWebPublications: %w", err) - } - - return out, err -} - -type GetWebPublicationRecordByPathResult struct { - EntitiesID int64 - EntitiesEID string - WebPublicationsVersion string - WebPublicationsPath string -} - -func GetWebPublicationRecordByPath(conn *sqlite.Conn, webPublicationsPath string) (GetWebPublicationRecordByPathResult, error) { - const query = `SELECT entities.id, entities.eid, web_publications.version, web_publications.path -FROM web_publications -JOIN entities ON web_publications.eid = entities.eid WHERE web_publications.path = :webPublicationsPath` - - var out GetWebPublicationRecordByPathResult - - before := func(stmt *sqlite.Stmt) { - stmt.SetText(":webPublicationsPath", webPublicationsPath) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - if i > 1 { - return errors.New("GetWebPublicationRecordByPath: more than one result return for a single-kind query") - } - - out.EntitiesID = stmt.ColumnInt64(0) - out.EntitiesEID = stmt.ColumnText(1) - out.WebPublicationsVersion = stmt.ColumnText(2) - out.WebPublicationsPath = stmt.ColumnText(3) - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: GetWebPublicationRecordByPath: %w", err) - } - - return out, err -} - -type GetWebPublicationsByIDResult struct { - EntitiesID int64 - EntitiesEID string - WebPublicationsVersion string - WebPublicationsPath string -} - -func GetWebPublicationsByID(conn *sqlite.Conn, entitiesEID string) ([]GetWebPublicationsByIDResult, error) { - const query = `SELECT entities.id, entities.eid, web_publications.version, web_publications.path -FROM web_publications -JOIN entities ON web_publications.eid = entities.eid WHERE entities.eid = :entitiesEID` - - var out []GetWebPublicationsByIDResult - - before := func(stmt *sqlite.Stmt) { - stmt.SetText(":entitiesEID", entitiesEID) - } - - onStep := func(i int, stmt *sqlite.Stmt) error { - out = append(out, GetWebPublicationsByIDResult{ - EntitiesID: stmt.ColumnInt64(0), - EntitiesEID: stmt.ColumnText(1), - WebPublicationsVersion: stmt.ColumnText(2), - WebPublicationsPath: stmt.ColumnText(3), - }) - - return nil - } - - err := sqlitegen.ExecStmt(conn, query, before, onStep) - if err != nil { - err = fmt.Errorf("failed query: GetWebPublicationsByID: %w", err) - } - - return out, err -} diff --git a/backend/mttnet/sitesql/queries.gensum b/backend/mttnet/sitesql/queries.gensum deleted file mode 100644 index 5ba8e91745..0000000000 --- a/backend/mttnet/sitesql/queries.gensum +++ /dev/null @@ -1,2 +0,0 @@ -srcs: 84683062c33b87172439a32b124ba8ec -outs: d05d8ee6ec5ace4e550935fcd4f2b3a3 diff --git a/backend/mttnet/sitesql/queries.go b/backend/mttnet/sitesql/queries.go deleted file mode 100644 index a3803a083a..0000000000 --- a/backend/mttnet/sitesql/queries.go +++ /dev/null @@ -1,294 +0,0 @@ -// Package sitesql implements all the database related functions. -package sitesql - -import ( - s "mintter/backend/daemon/storage" - "mintter/backend/pkg/sqlitegen" - "mintter/backend/pkg/sqlitegen/qb" - "os" -) - -var _ = generateQueries - -const ( - // SiteRegistrationLinkKey is the column name of the meta table where we store the registration link. - SiteRegistrationLinkKey = "site_registration_link" - // SiteTitleKey is the column name of the meta table where the site title (in case this node is a site) is stored. - SiteTitleKey = "site_title" - // SiteDescriptionKey is the column name of the meta table where the site description (in case this node is a site) is stored. - SiteDescriptionKey = "site_description" -) - -//go:generate gorun -tags codegen generateQueries -func generateQueries() error { - code, err := sqlitegen.CodegenQueries("sitesql", - qb.MakeQuery(s.Schema, "RegisterSite", sqlitegen.QueryKindExec, - "INSERT OR REPLACE INTO", s.ServedSites, qb.ListColShort( - s.ServedSitesHostname, - s.ServedSitesGroupID, - s.ServedSitesVersion, - s.ServedSitesOwnerID, - ), '\n', - "VALUES", qb.List( - qb.VarCol(s.ServedSitesHostname), - qb.SubQuery( - "SELECT", s.EntitiesID, - "FROM", s.Entities, - "WHERE", s.EntitiesEID, "=", qb.Var("group_eid", sqlitegen.TypeText), - ), - qb.VarCol(s.ServedSitesVersion), - qb.SubQuery( - "SELECT", s.PublicKeysID, - "FROM", s.PublicKeys, - "WHERE", s.PublicKeysPrincipal, "=", qb.VarCol(s.PublicKeysPrincipal), - ), - ), - ), - qb.MakeQuery(s.Schema, "GetSiteInfo", sqlitegen.QueryKindSingle, - "SELECT", - qb.Results( - qb.ResultCol(s.EntitiesEID), - qb.ResultCol(s.ServedSitesVersion), - qb.ResultCol(s.PublicKeysPrincipal), - ), '\n', - "FROM", s.ServedSites, '\n', - "JOIN", s.Entities, "ON", s.EntitiesID, "=", s.ServedSitesGroupID, '\n', - "JOIN", s.PublicKeys, "ON", s.PublicKeysPrincipal, "=", s.ServedSitesOwnerID, '\n', - "WHERE", s.ServedSitesHostname, "=", qb.VarCol(s.ServedSitesHostname), - ), - qb.MakeQuery(s.Schema, "AddSite", sqlitegen.QueryKindExec, - "INSERT OR REPLACE INTO", s.Sites, qb.ListColShort( - s.SitesAccountID, - s.SitesAddresses, - s.SitesHostname, - s.SitesRole, - ), '\n', - "VALUES", qb.List( - qb.SubQuery( - "SELECT", s.PublicKeysID, - "FROM", s.PublicKeys, - "WHERE", s.PublicKeysPrincipal, "=", qb.VarCol(s.PublicKeysPrincipal), - ), - qb.VarCol(s.SitesAddresses), - qb.VarCol(s.SitesHostname), - qb.VarCol(s.SitesRole), - ), - ), - qb.MakeQuery(s.Schema, "RemoveSite", sqlitegen.QueryKindExec, - "DELETE FROM", s.Sites, - "WHERE", s.SitesHostname, "=", qb.VarCol(s.SitesHostname), - ), - - qb.MakeQuery(s.Schema, "GetSite", sqlitegen.QueryKindSingle, - "SELECT", - qb.Results( - qb.ResultCol(s.SitesAddresses), - qb.ResultCol(s.SitesHostname), - qb.ResultCol(s.SitesRole), - qb.ResultCol(s.PublicKeysPrincipal), - ), '\n', - "FROM", s.Sites, '\n', - "JOIN", s.PublicKeys, "ON", s.PublicKeysID, "=", s.SitesAccountID, '\n', - "WHERE", s.SitesHostname, "=", qb.VarCol(s.SitesHostname), - ), - - qb.MakeQuery(s.Schema, "ListSites", sqlitegen.QueryKindMany, - "SELECT", - qb.Results( - qb.ResultCol(s.SitesAddresses), - qb.ResultCol(s.SitesHostname), - qb.ResultCol(s.SitesRole), - qb.ResultCol(s.PublicKeysPrincipal), - ), '\n', - "FROM", s.Sites, '\n', - "JOIN", s.PublicKeys, "ON", s.PublicKeysID, "=", s.SitesAccountID, - ), - - qb.MakeQuery(s.Schema, "SetSiteRegistrationLink", sqlitegen.QueryKindExec, - "INSERT OR REPLACE INTO", s.KV, qb.ListColShort( - s.KVKey, - s.KVValue, - ), '\n', - "VALUES", qb.List( - "'"+SiteRegistrationLinkKey+"'", - qb.Var("link", sqlitegen.TypeText), - ), - ), - - qb.MakeQuery(s.Schema, "GetSiteRegistrationLink", sqlitegen.QueryKindSingle, - "SELECT", qb.Results( - qb.ResultCol(s.KVValue), - ), - "FROM", s.KV, - "WHERE", s.KVKey, "='"+SiteRegistrationLinkKey+"'", - ), - - qb.MakeQuery(s.Schema, "SetSiteTitle", sqlitegen.QueryKindExec, - "INSERT OR REPLACE INTO", s.KV, qb.ListColShort( - s.KVKey, - s.KVValue, - ), '\n', - "VALUES", qb.List( - "'"+SiteTitleKey+"'", - qb.Var("title", sqlitegen.TypeText), - ), - ), - - qb.MakeQuery(s.Schema, "GetSiteTitle", sqlitegen.QueryKindSingle, - "SELECT", qb.Results( - qb.ResultCol(s.KVValue), - ), - "FROM", s.KV, - "WHERE", s.KVKey, "='"+SiteTitleKey+"'", - ), - - qb.MakeQuery(s.Schema, "SetSiteDescription", sqlitegen.QueryKindExec, - "INSERT OR REPLACE INTO", s.KV, qb.ListColShort( - s.KVKey, - s.KVValue, - ), '\n', - "VALUES", qb.List( - "'"+SiteDescriptionKey+"'", - qb.Var("description", sqlitegen.TypeText), - ), - ), - - qb.MakeQuery(s.Schema, "GetSiteDescription", sqlitegen.QueryKindSingle, - "SELECT", qb.Results( - qb.ResultCol(s.KVValue), - ), - "FROM", s.KV, - "WHERE", s.KVKey, "='"+SiteDescriptionKey+"'", - ), - - qb.MakeQuery(s.Schema, "AddToken", sqlitegen.QueryKindExec, - qb.Insert(s.InviteTokensToken, s.InviteTokensExpireTime, - s.InviteTokensRole), - ), - - qb.MakeQuery(s.Schema, "GetToken", sqlitegen.QueryKindSingle, - "SELECT", qb.Results( - qb.ResultCol(s.InviteTokensRole), - qb.ResultCol(s.InviteTokensExpireTime), - ), '\n', - "FROM", s.InviteTokens, - "WHERE", s.InviteTokensToken, "=", qb.VarCol(s.InviteTokensToken), - ), - - qb.MakeQuery(s.Schema, "RemoveToken", sqlitegen.QueryKindExec, - "DELETE FROM", s.InviteTokens, - "WHERE", s.InviteTokensToken, "=", qb.VarCol(s.InviteTokensToken), - ), - - qb.MakeQuery(s.Schema, "RemoveExpiredTokens", sqlitegen.QueryKindExec, - "DELETE FROM", s.InviteTokens, - "WHERE", s.InviteTokensExpireTime, "<", qb.SQLFunc("strftime", "'%s'", "'now'"), - ), - - qb.MakeQuery(s.Schema, "InsertMember", sqlitegen.QueryKindSingle, - "INSERT OR REPLACE INTO", s.SiteMembers, qb.ListColShort( - s.SiteMembersAccountID, - s.SiteMembersRole, - ), '\n', - "VALUES", qb.List( - qb.VarCol(s.SiteMembersAccountID), - qb.VarCol(s.SiteMembersRole), - ), '\n', - "RETURNING", qb.Results(s.SiteMembersRole), - ), - - qb.MakeQuery(s.Schema, "RemoveMember", sqlitegen.QueryKindExec, - "DELETE FROM", s.SiteMembers, - "WHERE", s.SiteMembersAccountID, "=", qb.SubQuery( - "SELECT", s.PublicKeysID, - "FROM", s.PublicKeys, - "WHERE", s.PublicKeysPrincipal, "=", qb.VarCol(s.PublicKeysPrincipal), - ), - ), - - qb.MakeQuery(s.Schema, "GetMember", sqlitegen.QueryKindSingle, - "SELECT", qb.Results( - qb.ResultCol(s.SiteMembersRole), - ), '\n', - "FROM", s.SiteMembers, '\n', - "WHERE", s.SiteMembersAccountID, "=", qb.SubQuery( - "SELECT", s.PublicKeysID, - "FROM", s.PublicKeys, - "WHERE", s.PublicKeysPrincipal, "=", qb.VarCol(s.PublicKeysPrincipal), - ), - ), - - qb.MakeQuery(s.Schema, "ListMembers", sqlitegen.QueryKindMany, - "SELECT", qb.Results( - qb.ResultCol(s.SiteMembersRole), - qb.ResultCol(s.PublicKeysPrincipal), - ), '\n', - "FROM", s.SiteMembers, '\n', - "JOIN", s.PublicKeys, "ON", s.PublicKeysID, "=", s.SiteMembersAccountID, - ), - - qb.MakeQuery(s.Schema, "InsertWebPublicationRecord", sqlitegen.QueryKindExec, - "INSERT INTO", s.WebPublications, qb.ListColShort( - s.WebPublicationsEID, - s.WebPublicationsVersion, - s.WebPublicationsPath, - ), '\n', - "VALUES", qb.List( - qb.VarCol(s.WebPublicationsEID), - qb.VarCol(s.WebPublicationsVersion), - qb.VarCol(s.WebPublicationsPath), - ), - ), - - qb.MakeQuery(s.Schema, "RemoveWebPublicationRecord", sqlitegen.QueryKindExec, - "DELETE FROM", s.WebPublications, - "WHERE", s.WebPublicationsEID, "=", qb.VarCol(s.EntitiesEID), - "AND", s.WebPublicationsVersion, "=", qb.VarCol(s.WebPublicationsVersion), - ), - - qb.MakeQuery(s.Schema, "ListWebPublications", sqlitegen.QueryKindMany, - "SELECT", qb.Results( - qb.ResultCol(s.EntitiesID), - qb.ResultCol(s.EntitiesEID), - qb.ResultCol(s.WebPublicationsVersion), - qb.ResultCol(s.WebPublicationsPath), - ), '\n', - "FROM", s.WebPublications, '\n', - "JOIN", s.Entities, "ON", s.WebPublicationsEID, "=", s.EntitiesEID, - ), - - qb.MakeQuery(s.Schema, "GetWebPublicationRecordByPath", sqlitegen.QueryKindSingle, - "SELECT", - qb.Results( - qb.ResultCol(s.EntitiesID), - qb.ResultCol(s.EntitiesEID), - qb.ResultCol(s.WebPublicationsVersion), - qb.ResultCol(s.WebPublicationsPath), - ), '\n', - "FROM", s.WebPublications, '\n', - "JOIN", s.Entities, "ON", s.WebPublicationsEID, "=", s.EntitiesEID, - "WHERE", s.WebPublicationsPath, "=", qb.VarCol(s.WebPublicationsPath), - ), - - qb.MakeQuery(s.Schema, "GetWebPublicationsByID", sqlitegen.QueryKindMany, - "SELECT", - qb.Results( - qb.ResultCol(s.EntitiesID), - qb.ResultCol(s.EntitiesEID), - qb.ResultCol(s.WebPublicationsVersion), - qb.ResultCol(s.WebPublicationsPath), - ), '\n', - "FROM", s.WebPublications, '\n', - "JOIN", s.Entities, "ON", s.WebPublicationsEID, "=", s.EntitiesEID, - "WHERE", s.EntitiesEID, "=", qb.VarCol(s.EntitiesEID), - ), - ) - if err != nil { - return err - } - if err := os.WriteFile("queries.gen.go", code, 0600); err != nil { - return err - } - - return nil -} diff --git a/backend/mttnet/sitesql/sitesql.go b/backend/mttnet/sitesql/sitesql.go deleted file mode 100644 index 49993306a8..0000000000 --- a/backend/mttnet/sitesql/sitesql.go +++ /dev/null @@ -1,61 +0,0 @@ -package sitesql - -import ( - "fmt" - "mintter/backend/core" - documents "mintter/backend/genproto/documents/v1alpha" - "mintter/backend/hyper/hypersql" - - "crawshaw.io/sqlite" -) - -// GetMemberRole gets a member role. Error if it does not exist. -func GetMemberRole(conn *sqlite.Conn, account core.Principal) (documents.Member_Role, error) { - member, err := GetMember(conn, account) - if err != nil { - return 0, err - } - - if member.SiteMembersRole == int64(documents.Member_ROLE_UNSPECIFIED) { - return 0, fmt.Errorf("could not get member for account %s: %w", account.String(), err) - } - - return documents.Member_Role(member.SiteMembersRole), nil -} - -// AddMember to the site. -func AddMember(conn *sqlite.Conn, publicKeysPrincipal []byte, siteMembersRole int64) (InsertMemberResult, error) { - key, err := ensurePublicKey(conn, publicKeysPrincipal) - if err != nil { - return InsertMemberResult{}, err - } - - return InsertMember(conn, key, siteMembersRole) -} - -// AddWebPublicationRecord ensuring entity ID exists. -func AddWebPublicationRecord(conn *sqlite.Conn, hyperEntitiesEID string, webPublicationRecordsDocumentVersion string, webPublicationRecordsPath string) error { - return InsertWebPublicationRecord(conn, hyperEntitiesEID, webPublicationRecordsDocumentVersion, webPublicationRecordsPath) -} - -func ensurePublicKey(conn *sqlite.Conn, key []byte) (int64, error) { - res, err := hypersql.PublicKeysLookupID(conn, key) - if err != nil { - return 0, err - } - - if res.PublicKeysID > 0 { - return res.PublicKeysID, nil - } - - ins, err := hypersql.PublicKeysInsert(conn, key) - if err != nil { - return 0, err - } - - if ins.PublicKeysID <= 0 { - panic("BUG: failed to insert key for some reason") - } - - return ins.PublicKeysID, nil -} diff --git a/backend/pkg/dqb/dqb.go b/backend/pkg/dqb/dqb.go new file mode 100644 index 0000000000..b2d1dbd397 --- /dev/null +++ b/backend/pkg/dqb/dqb.go @@ -0,0 +1,87 @@ +// Package dqb provides a dynamic query builder. +// This is an experiment to allow writing queries by hand and being able to validate them against a DB schema, +// to make sure they are valid and only reference correct tables and columns. +package dqb + +import ( + "context" + "fmt" + "sync" + + "crawshaw.io/sqlite/sqlitex" +) + +// GlobalQueries is a global storage of SQL queries. +var GlobalQueries Queries + +// Q returns a memoized function that returns a SQL query string. +func Q(fn func() string) LazyQuery { + return GlobalQueries.Q(fn) +} + +// Str returns a memoized string query that will be formatted lazily. +func Str(s string) LazyQuery { + return GlobalQueries.Str(s) +} + +// LazyQuery is a memoized function that returns a SQL query string. +type LazyQuery func() string + +// Queries is a storage of functions that memoizes the result of a function preparing a SQL query string. +type Queries struct { + funcs []*onceFunc +} + +// Q returns a memoized function that returns a SQL query string. +// The query will be dedented and SQL comments will be stripped. +func (q *Queries) Q(fn func() string) LazyQuery { + idx := len(q.funcs) + q.funcs = append(q.funcs, &onceFunc{fn: fn}) + return q.funcs[idx].Do +} + +// Str returns a memoized string query that will be formatted lazily. +func (q *Queries) Str(s string) LazyQuery { + return q.Q(func() string { return s }) +} + +// Test tests all the queries in the storage with a given connection +// by preparing them. This makes sure the queries are valid according to the DB schema. +func (q *Queries) Test(db *sqlitex.Pool) error { + conn, release, err := db.Conn(context.Background()) + if err != nil { + return err + } + defer release() + + for i := range q.funcs { + q := q.funcs[i].Do() + stmt, trailing, err := conn.PrepareTransient(q) + if err != nil { + return fmt.Errorf("failed preparing query: %w", err) + } + + if trailing != 0 { + return fmt.Errorf("query %s has some trailing bytes", q) + } + + if err := stmt.Finalize(); err != nil { + return err + } + } + + return nil +} + +type onceFunc struct { + once sync.Once + fn func() string + val string +} + +func (o *onceFunc) Do() string { + o.once.Do(func() { + o.val = sqlfmt(o.fn()) + }) + return o.val +} diff --git a/backend/pkg/dqb/dqb_test.go b/backend/pkg/dqb/dqb_test.go new file mode 100644 index 0000000000..b8d9c83368 --- /dev/null +++ b/backend/pkg/dqb/dqb_test.go @@ -0,0 +1,36 @@ +package dqb + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestQ(t *testing.T) { + var testQueries Queries + + var count int + var ( + qFoo = testQueries.Q(func() string { + count++ + return "foo" + }) + + qBar = testQueries.Q(func() string { + count++ + return "bar" + }) + ) + + require.Equal(t, "foo", qFoo()) + require.Equal(t, "foo", qFoo()) + require.Equal(t, "foo", qFoo()) + require.Equal(t, "foo", qFoo()) + + require.Equal(t, "bar", qBar()) + require.Equal(t, "bar", qBar()) + require.Equal(t, "bar", qBar()) + require.Equal(t, "bar", qBar()) + + require.Equal(t, 2, count, "functions must be memoized") +} diff --git a/backend/pkg/dqb/qfmt.go b/backend/pkg/dqb/qfmt.go new file mode 100644 index 0000000000..008da61777 --- /dev/null +++ b/backend/pkg/dqb/qfmt.go @@ -0,0 +1,85 @@ +package dqb + +import ( + "regexp" + "strings" +) + +var ( + whitespaceOnly = regexp.MustCompile("(?m)^[ \t]+$") + leadingWhitespace = regexp.MustCompile("(?m)(^[ \t]*)(?:[^ \t\n])") +) + +// sqlfmt removes any common leading whitespace from every line in text, +// and replaces tabs with spaces to make the output compatible with schema.sql +// +// This can be used to make multiline strings to line up with the left edge of +// the display, while still presenting them in the source code in indented +// form. +func sqlfmt(text string) string { + var margin string + + text = whitespaceOnly.ReplaceAllString(text, "") + indents := leadingWhitespace.FindAllStringSubmatch(text, -1) + + // Look for the longest leading string of spaces and tabs common to all + // lines. + for i, indent := range indents { + if i == 0 { + margin = indent[1] + } else if strings.HasPrefix(indent[1], margin) { + // Current line more deeply indented than previous winner: + // no change (previous winner is still on top). + continue + } else if strings.HasPrefix(margin, indent[1]) { + // Current line consistent with and no deeper than previous winner: + // it's the new winner. + margin = indent[1] + } else { + // Current line and previous winner have no common whitespace: + // there is no margin. + margin = "" + break + } + } + + if margin != "" { + text = regexp.MustCompile("(?m)^"+margin).ReplaceAllString(text, "") + } + + return removeSQLComments( + strings.Replace(text, "\t", " ", -1), + ) +} + +// removeSQLComments is written with the help of ChatGPT, but it seems to work. +// We don't need to store comments in the database file, but we want to use them for ourselves. +func removeSQLComments(sql string) string { + re := regexp.MustCompile(`('[^']*')|--.*|/\*[\s\S]*?\*/`) // Regular expression to match SQL comments and string literals + lines := strings.Split(sql, "\n") // Split SQL statement into lines + outLines := make([]string, 0, len(lines)) + for _, line := range lines { + line = re.ReplaceAllStringFunc(line, func(match string) string { + if strings.HasPrefix(match, "--") { + return "" // Remove single-line comments + } else if strings.HasPrefix(match, "/*") { + return "" // Remove multi-line comments + } else { + return match // Preserve string literals + } + }) + // Lines with only comments end up being empty, and we don't want those. + if strings.TrimSpace(line) == "" { + continue + } + // We don't want trailing new lines, because we'll be joining lines later. + line = strings.Trim(line, "\r\n") + // For more convenient formatting, all of our migration statement would have + // an extra tab at the beginning of the line, we can get rid of it. + if line[0] == '\t' { + line = line[1:] + } + outLines = append(outLines, line) + } + return strings.Join(outLines, "\n") // Join lines back together +} diff --git a/backend/syncing/discovery.go b/backend/syncing/discovery.go index f76672b538..47b5375c55 100644 --- a/backend/syncing/discovery.go +++ b/backend/syncing/discovery.go @@ -23,13 +23,20 @@ func (s *Service) DiscoverObject(ctx context.Context, obj hyper.EntityID, ver hy ctx, cancel := context.WithTimeout(ctx, defaultDiscoveryTimeout) defer cancel() - const maxProviders = 3 - c, err := obj.CID() if err != nil { return err } + // Arbitrary number of maximum providers + maxProviders := 5 + + // If we are looking for a specific version, we don't need to limit the number of providers, + // because we will short-circuit as soon as we found the desired version. + if ver != "" { + maxProviders = 0 + } + peers := s.bitswap.FindProvidersAsync(ctx, c, maxProviders) var wg sync.WaitGroup diff --git a/backend/syncing/syncing.go b/backend/syncing/syncing.go index 759e2ce19a..7f61de2c0d 100644 --- a/backend/syncing/syncing.go +++ b/backend/syncing/syncing.go @@ -49,10 +49,7 @@ type Service struct { bitswap Bitswap client NetDialFunc - // NoInbound disables syncing content from the remote peer to our peer. - // If false, then documents get synced in both directions. - NoInbound bool - mu sync.Mutex // Ensures only one sync loop is running at a time. + mu sync.Mutex // Ensures only one sync loop is running at a time. } const ( @@ -61,20 +58,19 @@ const ( defaultPeerSyncTimeout = time.Minute * 5 ) -// NewService creates a new syncing service. Users must call Start() to start the periodic syncing. -func NewService(log *zap.Logger, me core.Identity, db *sqlitex.Pool, blobs *hyper.Storage, bitswap Bitswap, client NetDialFunc, inDisable bool) *Service { +// NewService creates a new syncing service. Users should call Start() to start the periodic syncing. +func NewService(log *zap.Logger, me core.Identity, db *sqlitex.Pool, blobs *hyper.Storage, bitswap Bitswap, client NetDialFunc) *Service { svc := &Service{ warmupDuration: defaultWarmupDuration, syncInterval: defaultSyncInterval, peerSyncTimeout: defaultPeerSyncTimeout, - log: log, - db: db, - blobs: blobs, - me: me, - bitswap: bitswap, - client: client, - NoInbound: inDisable, + log: log, + db: db, + blobs: blobs, + me: me, + bitswap: bitswap, + client: client, } return svc @@ -112,6 +108,7 @@ func (s *Service) Start(ctx context.Context) (err error) { }() t := time.NewTimer(s.warmupDuration) + defer t.Stop() for { select { @@ -304,7 +301,7 @@ func (s *Service) syncObject(ctx context.Context, sess exchange.Fetcher, obj *p2 if err := kd.Verify(); err != nil { return fmt.Errorf("failed to verify key delegation: %w", err) } - kdblob, err := hyper.EncodeBlob(hyper.TypeKeyDelegation, kd) + kdblob, err := hyper.EncodeBlob(kd) if err != nil { return err } @@ -331,7 +328,7 @@ func (s *Service) syncObject(ctx context.Context, sess exchange.Fetcher, obj *p2 } } - changeBlob, err := hyper.EncodeBlob(hyper.TypeChange, vc.change) + changeBlob, err := hyper.EncodeBlob(vc.change) if err != nil { return err } @@ -357,12 +354,6 @@ func (s *Service) SyncWithPeer(ctx context.Context, device peer.ID, initialObjec return nil } - // Nodes such web sites can be configured to avoid automatic syncing with remote peers, - // unless explicitly asked to sync some specific object IDs. - if s.NoInbound && len(initialObjects) == 0 { - return nil - } - var filter map[hyper.EntityID]struct{} if initialObjects != nil { filter = make(map[hyper.EntityID]struct{}, len(initialObjects)) diff --git a/backend/syncing/syncing_test.go b/backend/syncing/syncing_test.go index a0ea65db44..5eebcd44c7 100644 --- a/backend/syncing/syncing_test.go +++ b/backend/syncing/syncing_test.go @@ -28,27 +28,8 @@ import ( func TestSync(t *testing.T) { t.Parallel() - type Node struct { - *mttnet.Node - - Syncer *Service - } - - newNode := func(name string) Node { - var n Node - - db := storage.MakeTestDB(t) - peer, stop := makeTestPeer(t, db, name) - t.Cleanup(stop) - n.Node = peer - - n.Syncer = NewService(must.Do2(zap.NewDevelopment()).Named(name), peer.ID(), db, peer.Blobs(), peer.Bitswap(), peer.Client, false) - - return n - } - - alice := newNode("alice") - bob := newNode("bob") + alice := makeTestNode(t, "alice") + bob := makeTestNode(t, "bob") ctx := context.Background() require.NoError(t, alice.Connect(ctx, bob.AddrInfo())) @@ -73,89 +54,42 @@ func TestSync(t *testing.T) { } } -// TODO(burdiyan): fix the test -// func TestSyncWithList(t *testing.T) { -// t.Parallel() - -// type Node struct { -// *mttnet.Node - -// Syncer *Service -// } - -// newNode := func(name string, inDisable bool) Node { -// var n Node - -// peer, stop := makeTestPeer(t, name) -// t.Cleanup(stop) -// n.Node = peer - -// n.Syncer = NewService(must.Do2(zap.NewDevelopment()).Named(name), peer.ID(), peer.VCS(), peer.Bitswap(), peer.Client, inDisable) - -// return n -// } -// alice := newNode("alice", false) -// bob := newNode("bob", true) -// ctx := context.Background() - -// require.NoError(t, alice.Connect(ctx, bob.AddrInfo())) - -// var alicePerma vcs.EncodedPermanode -// var wantDatoms []sqlitevcs.Datom -// var publicVersion string -// { -// conn, release, err := alice.VCS().Conn(ctx) -// require.NoError(t, err) - -// err = conn.WithTx(true, func() error { -// clock := hlc.NewClock() -// perma, err := vcs.EncodePermanode(sqlitevcs.NewDocumentPermanode(alice.ID().AccountID(), clock.Now())) -// alicePerma = perma -// require.NoError(t, err) -// obj := conn.NewObject(perma) -// idLocal := conn.EnsureIdentity(alice.ID()) -// change := conn.NewChange(obj, idLocal, nil, clock) - -// wantDatoms = []sqlitevcs.Datom{ -// vcs.NewDatom(vcs.RootNode, "title", "This is a title", clock.Now().Pack(), 123), -// } - -// conn.AddDatoms(obj, change, wantDatoms...) -// conn.SaveVersion(obj, "main", idLocal, sqlitevcs.LocalVersion{change}) -// conn.EncodeChange(change, alice.ID().DeviceKey()) -// version := conn.GetVersion(obj, "main", idLocal) -// publicVersion = conn.LocalVersionToPublic(version).String() -// return nil -// }) -// release() -// require.NoError(t, err) -// } - -// require.NoError(t, bob.Syncer.SyncWithPeer(ctx, alice.ID().DeviceKey().CID(), alicePerma.ID...)) - -// { -// conn, release, err := bob.VCS().Conn(ctx) -// require.NoError(t, err) - -// err = conn.WithTx(false, func() error { -// obj := conn.LookupPermanode(alicePerma.ID) -// idLocal := conn.LookupIdentity(bob.ID()) -// version := conn.GetVersion(obj, "main", idLocal) -// cs := conn.ResolveChangeSet(obj, version) - -// var i int -// it := conn.QueryObjectDatoms(obj, cs) -// for it.Next() { -// i++ -// } -// require.Equal(t, len(wantDatoms), i, "must get the same number of datoms as in the original object") - -// return nil -// }) -// release() -// require.NoError(t, err) -// } -// } +func TestSyncWithList(t *testing.T) { + t.Parallel() + + alice := makeTestNode(t, "alice") + bob := makeTestNode(t, "bob") + ctx := context.Background() + + entityID := hyper.EntityID("alice-test-entity") + { + e := hyper.NewEntity(entityID) + hb, err := e.CreateChange(e.NextTimestamp(), alice.ID().DeviceKey(), getDelegation(ctx, alice.ID(), alice.Blobs()), map[string]any{ + "title": "This is a title of a fake test entity", + }) + require.NoError(t, err) + require.NoError(t, alice.Blobs().SaveBlob(ctx, hb)) + } + // Create another entity for alice to make sure we only sync one entity. + { + e := hyper.NewEntity("another-entity") + hb, err := e.CreateChange(e.NextTimestamp(), alice.ID().DeviceKey(), getDelegation(ctx, alice.ID(), alice.Blobs()), map[string]any{ + "title": "This is a title of another fake test entity", + }) + require.NoError(t, err) + require.NoError(t, alice.Blobs().SaveBlob(ctx, hb)) + } + + require.NoError(t, alice.Connect(ctx, bob.AddrInfo())) + + require.NoError(t, bob.Syncer.SyncWithPeer(ctx, alice.ID().DeviceKey().PeerID(), entityID)) + + list, err := bob.Blobs().ListEntities(ctx, "") + require.NoError(t, err) + + require.Len(t, list, 3, "bob must have synced only one entity from alice") // 3 = bob's account + alice's account + alice's entity + require.Equal(t, entityID, list[2], "bob must have synced alice's entity") +} func makeTestPeer(t *testing.T, db *sqlitex.Pool, name string) (*mttnet.Node, context.CancelFunc) { u := coretest.NewTester(name) @@ -175,7 +109,7 @@ func makeTestPeer(t *testing.T, db *sqlitex.Pool, name string) (*mttnet.Node, co errc := make(chan error, 1) ctx, cancel := context.WithCancel(context.Background()) f := future.New[*mttnet.Node]() - _ = mttnet.NewServer(ctx, config.Default().Site, f.ReadOnly, nil, nil) + require.NoError(t, f.Resolve(n)) go func() { errc <- n.Start(ctx) @@ -225,3 +159,22 @@ func getDelegation(ctx context.Context, me core.Identity, blobs *hyper.Storage) return out } + +type testNode struct { + *mttnet.Node + + Syncer *Service +} + +func makeTestNode(t *testing.T, name string) testNode { + var n testNode + + db := storage.MakeTestDB(t) + peer, stop := makeTestPeer(t, db, name) + t.Cleanup(stop) + n.Node = peer + + n.Syncer = NewService(must.Do2(zap.NewDevelopment()).Named(name), peer.ID(), db, peer.Blobs(), peer.Bitswap(), peer.Client) + + return n +} diff --git a/backend/wallet/wallet_test.go b/backend/wallet/wallet_test.go index 13839044a5..6bcf1fa134 100644 --- a/backend/wallet/wallet_test.go +++ b/backend/wallet/wallet_test.go @@ -60,7 +60,7 @@ func TestRequestLndHubInvoice(t *testing.T) { ctx := context.Background() require.Eventually(t, func() bool { _, ok := bob.net.Get(); return ok }, 5*time.Second, 1*time.Second) - cid := bob.net.MustGet().ID().Account().CID() + cid := bob.net.MustGet().ID().Account().Principal() var amt uint64 = 23 var wrongAmt uint64 = 24 var memo = "test invoice" @@ -100,7 +100,7 @@ func TestRequestP2PInvoice(t *testing.T) { require.NoError(t, alice.net.MustGet().Connect(ctx, bob.net.MustGet().AddrInfo())) require.Eventually(t, func() bool { _, ok := bob.net.Get(); return ok }, 3*time.Second, 1*time.Second) - cid := bob.net.MustGet().ID().Account().CID() + cid := bob.net.MustGet().ID().Account().Principal() var amt uint64 = 23 var wrongAmt uint64 = 24 var memo = "test invoice" @@ -168,7 +168,6 @@ func makeTestPeer(t *testing.T, u coretest.Tester, db *sqlitex.Pool) (*mttnet.No errc := make(chan error, 1) ctx, cancel := context.WithCancel(context.Background()) f := future.New[*mttnet.Node]() - _ = mttnet.NewServer(ctx, config.Default().Site, f.ReadOnly, nil, nil) require.NoError(t, f.Resolve(n)) go func() { errc <- n.Start(ctx) diff --git a/build/rules/mintter/mintter.build_defs b/build/rules/mintter/mintter.build_defs index a8fe91fd3b..758c0e8c27 100644 --- a/build/rules/mintter/mintter.build_defs +++ b/build/rules/mintter/mintter.build_defs @@ -1,54 +1,60 @@ -def mtt_proto_codegen(srcs: list, languages: list): - subinclude("//build/rules/codegen:defs") +subinclude("//build/rules/codegen:defs") + +def mtt_go_proto(name, srcs): + pbouts = [x.replace(".proto", ".pb.go") for x in srcs] + grpcouts = [x.replace(".proto", "_grpc.pb.go") for x in srcs] + generated( + name = name, + srcs = srcs, + outs = pbouts + grpcouts, + cmd = """ +$TOOLS_PROTOC -I proto \ +--plugin=protoc-gen-go=$TOOLS_PROTOC_GEN_GO \ +--plugin=protoc-gen-go-grpc=$TOOLS_PROTOC_GEN_GO_GRPC \ +--go_out=module=mintter:$WORKSPACE \ +--go-grpc_out=module=mintter,require_unimplemented_servers=false:$WORKSPACE \ +$SRCS +""", + out_dir = "//" + package_name().replace("proto", "backend/genproto"), + tools = [ + "//build/nix:protoc", + "//build/tools:protoc-gen-go", + "//build/tools:protoc-gen-go-grpc", + ], + ) + +def mtt_js_proto(name, srcs): + generated( + name = "js", + srcs = srcs, + outs = [x.replace(".proto", "_pb.ts") for x in srcs] + [x.replace(".proto", "_connect.ts") for x in srcs], + cmd = """ +$TOOLS_PROTOC -I proto \ +--plugin=protoc-gen-es=$TOOLS_PROTOC_GEN_ES \ +--plugin=protoc-gen-connect-es=$TOOLS_PROTOC_GEN_CONNECT_ES \ +--es_opt=target=ts,import_extension=none \ +--connect-es_opt=target=ts,import_extension=none \ +--es_out=frontend/packages/shared/src/client/.generated/ \ +--connect-es_out=frontend/packages/shared/src/client/.generated/ \ +$SRCS +""", + out_dir = "//" + package_name().replace("proto", "frontend/packages/shared/src/client/.generated"), + tools = [ + "//build/nix:protoc", + "//build/tools:protoc-gen-es", + "//build/tools:protoc-gen-connect-es", + ], + ) +def mtt_proto_codegen(srcs: list, languages: list): for s in srcs: if "//" in s or ":" in s: log.fatal("proto srcs can only be files") for lang in languages: if lang == "go": - pbouts = [x.replace(".proto", ".pb.go") for x in srcs] - grpcouts = [x.replace(".proto", "_grpc.pb.go") for x in srcs] - generated( - name = "go", - srcs = srcs, - outs = pbouts + grpcouts, - cmd = """ -$TOOLS_PROTOC -I proto \ - --plugin=protoc-gen-go=$TOOLS_PROTOC_GEN_GO \ - --plugin=protoc-gen-go-grpc=$TOOLS_PROTOC_GEN_GO_GRPC \ - --go_out=module=mintter:$WORKSPACE \ - --go-grpc_out=module=mintter,require_unimplemented_servers=false:$WORKSPACE \ - $SRCS -""", - out_dir = "//" + package_name().replace("proto", "backend/genproto"), - tools = [ - "//build/nix:protoc", - "//build/tools:protoc-gen-go", - "//build/tools:protoc-gen-go-grpc", - ], - ) + mtt_go_proto(name = "go", srcs = srcs) elif lang == "js": - generated( - name = "js", - srcs = srcs, - outs = [x.replace(".proto", "_pb.ts") for x in srcs] + [x.replace(".proto", "_connect.ts") for x in srcs], - cmd = """ -$TOOLS_PROTOC -I proto \ - --plugin=protoc-gen-es=$TOOLS_PROTOC_GEN_ES \ - --plugin=protoc-gen-connect-es=$TOOLS_PROTOC_GEN_CONNECT_ES \ - --es_opt=target=ts,import_extension=none \ - --connect-es_opt=target=ts,import_extension=none \ - --es_out=frontend/packages/shared/src/client/.generated/ \ - --connect-es_out=frontend/packages/shared/src/client/.generated/ \ - $SRCS -""", - out_dir = "//" + package_name().replace("proto", "frontend/packages/shared/src/client/.generated"), - tools = [ - "//build/nix:protoc", - "//build/tools:protoc-gen-es", - "//build/tools:protoc-gen-connect-es", - ], - ) + mtt_js_proto(name = "js", srcs = srcs) else: log.fatal("unsupported proto lang " + lang) diff --git a/dev b/dev index eaadb0a45e..b2cd5013af 100755 --- a/dev +++ b/dev @@ -97,7 +97,7 @@ def main(): return run( "yarn site", args=args, - env={**os.environ, "GW_NEXT_HOST": "http://localhost:3000"}, + env={**os.environ, "HM_BASE_URL": "http://localhost:3000"}, ) @cmd(cmds, "build-site", "Build site app for production.") @@ -109,7 +109,7 @@ def main(): return run( "yarn site:prod", args=args, - # env={**os.environ, "GW_NEXT_HOST": "http://localhost:3000"}, + # env={**os.environ, "HM_BASE_URL": "http://localhost:3000"}, ) @cmd(cmds, "frontend-validate", "Formats, Validates") diff --git a/docker-compose-groups.yml b/docker-compose-groups.yml index 09d90fbd66..beebc4b059 100644 --- a/docker-compose-groups.yml +++ b/docker-compose-groups.yml @@ -1,4 +1,4 @@ -version: '3.8' +version: "3.8" networks: internal_network: driver: bridge # the default @@ -9,15 +9,15 @@ services: depends_on: - minttersite ports: - - '80:80' - - '443:443' - - '443:443/udp' + - "80:80" + - "443:443" + - "443:443/udp" networks: - internal_network environment: - - 'MTT_SITE_HOSTNAME=${MTT_SITE_HOSTNAME:-http://nextjs}' - - 'MTT_SITE_BACKEND_GRPCWEB_PORT=${MTT_SITE_BACKEND_GRPCWEB_PORT:-56001}' - - 'MTT_SITE_LOCAL_PORT=${MTT_SITE_LOCAL_PORT:-3000}' + - "MTT_SITE_HOSTNAME=${MTT_SITE_HOSTNAME:-http://nextjs}" + - "MTT_SITE_BACKEND_GRPCWEB_PORT=${MTT_SITE_BACKEND_GRPCWEB_PORT:-56001}" + - "MTT_SITE_LOCAL_PORT=${MTT_SITE_LOCAL_PORT:-3000}" volumes: - ${MTT_SITE_WORKSPACE:-~/.mtt-site}/proxy/data:/data - ${MTT_SITE_WORKSPACE:-~/.mtt-site}/proxy/config:/config @@ -31,38 +31,36 @@ services: networks: - internal_network ports: - - '${MTT_SITE_LOCAL_PORT:-3000}:${MTT_SITE_LOCAL_PORT:-3000}' + - "${MTT_SITE_LOCAL_PORT:-3000}:${MTT_SITE_LOCAL_PORT:-3000}" restart: unless-stopped volumes: - ${MTT_SITE_WORKSPACE:-~/.mtt-site}/nextjs:/data:rw environment: - - 'GW_NEXT_HOST=${MTT_SITE_HOSTNAME:-http://nextjs}' - - 'NEXT_PUBLIC_GRPC_HOST=http://minttersite:${MTT_SITE_BACKEND_GRPCWEB_PORT:-56001}' - - 'NEXT_PUBLIC_LN_HOST=${MTT_SITE_LN_HOST:-https://ln.mintter.com}' + - "HM_BASE_URL=${MTT_SITE_HOSTNAME:-http://nextjs}" + - "NEXT_PUBLIC_GRPC_HOST=http://minttersite:${MTT_SITE_BACKEND_GRPCWEB_PORT:-56001}" + - "NEXT_PUBLIC_LN_HOST=${MTT_SITE_LN_HOST:-https://ln.mintter.com}" minttersite: - image: mintter/mintterd:latest + image: mintter/mintter-site:latest restart: unless-stopped container_name: ${MTT_SITE_DAEMON_CONTAINER_NAME:-minttersite} ports: - - '56000:56000' - - '56000:56000/udp' - - '56002:56002' + - "56000:56000" + - "56000:56000/udp" + - "56002:56002" networks: - internal_network volumes: - ${MTT_SITE_WORKSPACE:-~/.mtt-site}/backend:/.mttsite:rw command: - - 'mintterd' - - '-repo-path=/.mttsite' - - '-lndhub.mainnet' - - '-p2p.port=56000' - - '--http-port=${MTT_SITE_BACKEND_GRPCWEB_PORT:-56001}' - - '-grpc-port=56002' - - '-p2p.public-reachability' - - '-p2p.no-relay' - - '-p2p.announce-addrs=/dns4/${MTT_SITE_DNS}/tcp/56000,/dns4/${MTT_SITE_DNS}/udp/56000/quic,/dns4/${MTT_SITE_DNS}/udp/56000/quic-v1' - - '-site.hostname=${MTT_SITE_HOSTNAME:-http://nextjs}' - - '-identity.no-account-wait' - - '${MTT_SITE_ADDITIONAL_FLAGS}' + - "mintter-site" + - "${MTT_SITE_HOSTNAME:-http://nextjs}" + - "-data-dir=/.mttsite" + - "-lndhub.mainnet" + - "-p2p.port=56000" + - "--http.port=${MTT_SITE_BACKEND_GRPCWEB_PORT:-56001}" + - "-grpc.port=56002" + - "-p2p.no-relay" + - "-p2p.announce-addrs=/dns4/${MTT_SITE_DNS}/tcp/56000,/dns4/${MTT_SITE_DNS}/udp/56000/quic,/dns4/${MTT_SITE_DNS}/udp/56000/quic-v1" + - "${MTT_SITE_ADDITIONAL_FLAGS}" diff --git a/docker-compose.yml b/docker-compose.yml index faa60ec71d..53a6b34d6d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.8' +version: "3.8" networks: internal_network: driver: bridge # the default @@ -9,15 +9,15 @@ services: depends_on: - minttersite ports: - - '80:80' - - '443:443' - - '443:443/udp' + - "80:80" + - "443:443" + - "443:443/udp" networks: - internal_network environment: - - 'MTT_SITE_HOSTNAME=${MTT_SITE_HOSTNAME:-http://nextjs}' - - 'MTT_SITE_BACKEND_GRPCWEB_PORT=${MTT_SITE_BACKEND_GRPCWEB_PORT:-56001}' - - 'MTT_SITE_LOCAL_PORT=${MTT_SITE_LOCAL_PORT:-3000}' + - "MTT_SITE_HOSTNAME=${MTT_SITE_HOSTNAME:-http://nextjs}" + - "MTT_SITE_BACKEND_GRPCWEB_PORT=${MTT_SITE_BACKEND_GRPCWEB_PORT:-56001}" + - "MTT_SITE_LOCAL_PORT=${MTT_SITE_LOCAL_PORT:-3000}" volumes: - ${MTT_SITE_WORKSPACE:-~/.mtt-site}/proxy/data:/data - ${MTT_SITE_WORKSPACE:-~/.mtt-site}/proxy/config:/config @@ -31,39 +31,39 @@ services: networks: - internal_network ports: - - '${MTT_SITE_LOCAL_PORT:-3000}:${MTT_SITE_LOCAL_PORT:-3000}' + - "${MTT_SITE_LOCAL_PORT:-3000}:${MTT_SITE_LOCAL_PORT:-3000}" restart: unless-stopped volumes: - ${MTT_SITE_WORKSPACE:-~/.mtt-site}/nextjs:/data:rw environment: - - 'GW_NEXT_HOST=${MTT_SITE_HOSTNAME:-http://nextjs}' - - 'NEXT_PUBLIC_GRPC_HOST=http://minttersite:${MTT_SITE_BACKEND_GRPCWEB_PORT:-56001}' - - 'NEXT_PUBLIC_LN_HOST=${MTT_SITE_LN_HOST:-https://ln.mintter.com}' + - "HM_BASE_URL=${MTT_SITE_HOSTNAME:-http://nextjs}" + - "NEXT_PUBLIC_GRPC_HOST=http://minttersite:${MTT_SITE_BACKEND_GRPCWEB_PORT:-56001}" + - "NEXT_PUBLIC_LN_HOST=${MTT_SITE_LN_HOST:-https://ln.mintter.com}" minttersite: image: mintter/mintterd:latest restart: unless-stopped container_name: minttersite ports: - - '56000:56000' - - '56000:56000/udp' - - '56002:56002' + - "56000:56000" + - "56000:56000/udp" + - "56002:56002" networks: - internal_network volumes: - ${MTT_SITE_WORKSPACE:-~/.mtt-site}/backend:/.mttsite:rw command: - - 'mintterd' - - '-repo-path=/.mttsite' - - '-lndhub.mainnet' - - '-p2p.port=56000' - - '--http-port=${MTT_SITE_BACKEND_GRPCWEB_PORT:-56001}' - - '-grpc-port=56002' - - '-p2p.public-reachability' - - '-p2p.no-relay' - - '-p2p.announce-addrs=/dns4/${MTT_SITE_DNS}/tcp/56000,/dns4/${MTT_SITE_DNS}/udp/56000/quic,/dns4/${MTT_SITE_DNS}/udp/56000/quic-v1' - - '-site.hostname=${MTT_SITE_HOSTNAME:-http://nextjs}' - - '-site.owner-id=${MTT_SITE_OWNER_ACCOUNT_ID}' - - '${MTT_SITE_NOWAIT_FLAG}' - - '${MTT_SITE_ADDITIONAL_FLAGS}' + - "mintterd" + - "-data-dir=/.mttsite" + - "-lndhub.mainnet" + - "-p2p.port=56000" + - "--http.port=${MTT_SITE_BACKEND_GRPCWEB_PORT:-56001}" + - "-grpc.port=56002" + - "-p2p.public-reachability" + - "-p2p.no-relay" + - "-p2p.announce-addrs=/dns4/${MTT_SITE_DNS}/tcp/56000,/dns4/${MTT_SITE_DNS}/udp/56000/quic,/dns4/${MTT_SITE_DNS}/udp/56000/quic-v1" + - "-site.hostname=${MTT_SITE_HOSTNAME:-http://nextjs}" + - "-site.owner-id=${MTT_SITE_OWNER_ACCOUNT_ID}" + - "${MTT_SITE_NOWAIT_FLAG}" + - "${MTT_SITE_ADDITIONAL_FLAGS}" diff --git a/frontend/apps/desktop/src/api.ts b/frontend/apps/desktop/src/api.ts index 049d6ca245..3c1c49556b 100644 --- a/frontend/apps/desktop/src/api.ts +++ b/frontend/apps/desktop/src/api.ts @@ -113,6 +113,14 @@ let windowsState = (store.get('WindowState-v002') as Record) || ({} as Record) +function getAWindow() { + const focused = getFocusedWindow() + if (focused) return focused + const allWins = Object.values(allWindows) + const window: BrowserWindow | undefined = allWins[allWins.length - 1] + return window +} + export function openInitialWindows() { if (!Object.keys(windowsState).length) { trpc.createAppWindow({routes: [{key: 'home'}]}) @@ -365,6 +373,8 @@ export const router = t.router({ ) .mutation(async ({input}) => { const windowId = input.id || `window.${windowIdCount++}.${Date.now()}` + const win = getAWindow() + const prevWindowBounds = win?.getBounds() const bounds = input.bounds ? input.bounds : { @@ -523,11 +533,6 @@ export type AppInfo = { platform: () => typeof process.platform arch: () => typeof process.arch } -setTimeout(() => { - handleUrlOpen( - 'hm://connect-peer/12D3KooWBR9HQQFMiSy3DtFmumv6Jcm8sXwEB5bmpXMrFT6ENTck', - ) -}, 10000) export function handleUrlOpen(url: string) { log('[Deep Link Open]: ', url) diff --git a/frontend/apps/desktop/src/daemon.ts b/frontend/apps/desktop/src/daemon.ts index e60e1732e4..b87c7148c6 100644 --- a/frontend/apps/desktop/src/daemon.ts +++ b/frontend/apps/desktop/src/daemon.ts @@ -50,16 +50,16 @@ let goDaemonExecutablePath = console.log(`== ~ goDaemonExecutablePath:`, goDaemonExecutablePath) const daemonArguments = [ - '-http-port', + '-http.port', String(BACKEND_HTTP_PORT), - '-grpc-port', + '-grpc.port', String(BACKEND_GRPC_PORT), '-p2p.port', String(BACKEND_P2P_PORT), - '-repo-path', + '-data-dir', userDataDir, ] logger.info('Launching daemon:', goDaemonExecutablePath, daemonArguments) diff --git a/frontend/apps/site/Dockerfile b/frontend/apps/site/Dockerfile index 37f50612b8..047761d8b6 100644 --- a/frontend/apps/site/Dockerfile +++ b/frontend/apps/site/Dockerfile @@ -1,5 +1,5 @@ # Build from the root with `docker build -t nextjs-docker . -f ./frontend/apps/site/Dockerfile`. -# docker run -e GW_NEXT_HOST=http://127.0.0.1:56001 -e NEXT_PUBLIC_GRPC_HOST=http://127.0.0.1:56001 -it -p 3000:3000 --rm --name nextjs nextjs-docker:latest +# docker run -e HM_BASE_URL=http://127.0.0.1:56001 -e NEXT_PUBLIC_GRPC_HOST=http://127.0.0.1:56001 -it -p 3000:3000 --rm --name nextjs nextjs-docker:latest FROM node:18-alpine AS builder RUN apk add git diff --git a/frontend/apps/site/README.md b/frontend/apps/site/README.md index c1d2fd8a75..5f00498cab 100644 --- a/frontend/apps/site/README.md +++ b/frontend/apps/site/README.md @@ -21,12 +21,12 @@ separate directories: 1. **Terminal 2: Site Backend** ```shell - ./dev run-backend -site.hostname "http://127.0.0.1:56100" --http-port 56001 -grpc-port 56002 -p2p.port 56000 -repo-path ~/.mttsite -site.owner-id -site.title testsite -identity.no-account-wait -syncing.disable-inbound + ./dev run-backend -site.hostname "http://127.0.0.1:56100" --http.port 56001 -grpc.port 56002 -p2p.port 56000 -data-dir ~/.mttsite -site.owner-id -site.title testsite -identity.no-account-wait -syncing.disable-inbound ``` > (make sure to replace `` with your desktop app's account ID) 1. **Terminal 3: Site App** ```shell - NEXT_PUBLIC_GRPC_HOST="http://localhost:56001/" PORT=56100 GW_NEXT_HOST="http://127.0.0.1:56100/" yarn site + NEXT_PUBLIC_GRPC_HOST="http://localhost:56001/" PORT=56100 HM_BASE_URL="http://127.0.0.1:56100/" yarn site ``` diff --git a/frontend/apps/site/client.ts b/frontend/apps/site/client.ts index 3897f5cdcd..cacb2af7a3 100644 --- a/frontend/apps/site/client.ts +++ b/frontend/apps/site/client.ts @@ -13,7 +13,6 @@ import { import { Accounts, Publications, - WebSite, Daemon, Networking, Groups, @@ -52,7 +51,7 @@ function getGRPCHost() { return 'http://127.0.0.1:56001' } - return 'https://gateway.mintter.com' + return 'https://hyper.media' } const IS_DEV = process.env.NODE_ENV == 'development' @@ -78,34 +77,7 @@ export const transport = createGrpcWebTransport({ interceptors: IS_DEV ? DEV_INTERCEPTORS : [prodInter], }) -function augmentWebsiteClient( - client: ReturnType>, -) { - return { - ...client, - getPath: async (req: {path: string}) => { - const result = await client.getPath(req).catch((error) => { - if (error.rawMessage?.match('Could not get record for path')) { - return null - } - if ( - error.rawMessage?.match( - 'Could not get local document although was found', - ) - ) { - return null - } - throw error - }) - return result - }, - } -} - export const publicationsClient = createPromiseClient(Publications, transport) -export const localWebsiteClient = augmentWebsiteClient( - createPromiseClient(WebSite, transport), -) export const accountsClient = createPromiseClient(Accounts, transport) export const groupsClient = createPromiseClient(Groups, transport) export const daemonClient = createPromiseClient(Daemon, transport) diff --git a/frontend/apps/site/get-site-info.ts b/frontend/apps/site/get-site-info.ts index b37a7125bf..5520cc15aa 100644 --- a/frontend/apps/site/get-site-info.ts +++ b/frontend/apps/site/get-site-info.ts @@ -1,14 +1,3 @@ -import {SiteInfo} from '@mintter/shared' -import {localWebsiteClient} from './client' - export async function getSiteInfo() { - if (process.env.GW_NEXT_HOST) { - try { - let info: SiteInfo = await localWebsiteClient.getSiteInfo({}) - return info - } catch (error) { - return null - } - } return null } diff --git a/frontend/apps/site/pages/_app.tsx b/frontend/apps/site/pages/_app.tsx index 2279896faa..64f58917ef 100644 --- a/frontend/apps/site/pages/_app.tsx +++ b/frontend/apps/site/pages/_app.tsx @@ -31,7 +31,7 @@ import {Toaster} from 'react-hot-toast' export default trpc.withTRPC(App) -const isMintterSite = process.env.GW_NEXT_HOST === 'https://mintter.com' +const isMintterSite = process.env.HM_BASE_URL === 'https://mintter.com' const hostIconPrefix = isMintterSite ? '/mintter-icon' : '/generic-icon' export type EveryPageProps = { diff --git a/frontend/apps/site/pages/api/site-info.tsx b/frontend/apps/site/pages/api/site-info.tsx index 847b2077e0..d368fe2191 100644 --- a/frontend/apps/site/pages/api/site-info.tsx +++ b/frontend/apps/site/pages/api/site-info.tsx @@ -1,8 +1,8 @@ -// test page for groupsClient.getSiteInfo({ hostname: process.env.GW_NEXT_HOST }) so we can see if this works in production +// test page for groupsClient.getSiteInfo({ hostname: process.env.HM_BASE_URL }) so we can see if this works in production import {groupsClient} from 'client' import {NextApiRequest, NextApiResponse} from 'next' -const gatewayHostWithProtocol = process.env.GW_NEXT_HOST +const gatewayHostWithProtocol = process.env.HM_BASE_URL const gatewayHost = new URL(gatewayHostWithProtocol || '').hostname console.log('â„¹ï¸ site info! ', { diff --git a/frontend/apps/site/pages/d/[docId].tsx b/frontend/apps/site/pages/d/[docEid].tsx similarity index 60% rename from frontend/apps/site/pages/d/[docId].tsx rename to frontend/apps/site/pages/d/[docEid].tsx index 8ac7e8861b..3269de9406 100644 --- a/frontend/apps/site/pages/d/[docId].tsx +++ b/frontend/apps/site/pages/d/[docEid].tsx @@ -5,9 +5,9 @@ import { } from 'next' import PublicationPage from 'publication-page' import {setAllowAnyHostGetCORS} from 'server/cors' -import {impatiently} from 'server/impatiently' import {useRequiredRouteQuery, useRouteQuery} from 'server/router-queries' import {getPageProps, serverHelpers} from 'server/ssr-helpers' +import {createHmId} from '@mintter/shared' function getDocSlugUrl( pathName: string | undefined, @@ -27,7 +27,7 @@ export default function IDPublicationPage( ) { return ( ) @@ -37,37 +37,37 @@ export const getServerSideProps: GetServerSideProps = async ( context: GetServerSidePropsContext, ) => { const {params, query} = context - let docId = params?.docId ? String(params.docId) : undefined + let docEid = params?.docEid ? String(params.docEid) : undefined let version = query.v ? String(query.v) : null - setAllowAnyHostGetCORS(context.res) - if (!docId) return {notFound: true} as const + if (!docEid) return {notFound: true} as const + + const docId = createHmId('d', docEid) const helpers = serverHelpers({}) - const docRecord = await helpers.publication.getDocRecord.fetch({ - documentId: docId, - }) - if (docRecord) { - return { - redirect: { - temporary: true, - destination: getDocSlugUrl( - docRecord.path, - docId, - version || docRecord.versionId, - ), - }, - props: {}, - } as const - } - await impatiently( - helpers.publication.get.prefetch({ - documentId: docId, - versionId: version || '', - }), - ) + // if (docRecord) { + // // old redirect to pretty URL behavior + // return { + // redirect: { + // temporary: true, + // destination: getDocSlugUrl( + // docRecord.path, + // docId, + // version || docRecord.versionId, + // ), + // }, + // props: {}, + // } as const + // } + + // await impatiently( + // helpers.publication.get.prefetch({ + // documentId: docId, + // versionId: version || '', + // }), + // ) return { props: await getPageProps(helpers, {}), diff --git a/frontend/apps/site/pages/g/[groupId]/[pathName].tsx b/frontend/apps/site/pages/g/[groupEid]/[pathName].tsx similarity index 96% rename from frontend/apps/site/pages/g/[groupId]/[pathName].tsx rename to frontend/apps/site/pages/g/[groupEid]/[pathName].tsx index fe93e3be5d..ead544d6ff 100644 --- a/frontend/apps/site/pages/g/[groupId]/[pathName].tsx +++ b/frontend/apps/site/pages/g/[groupEid]/[pathName].tsx @@ -39,7 +39,7 @@ export const getServerSideProps: GetServerSideProps< EveryPageProps & GroupPubPageProps > = async (context) => { const pathName = (context.params?.pathName as string) || '' - const groupEid = (context.params?.groupId as string) || '' + const groupEid = (context.params?.groupEid as string) || '' const groupId = createHmId('g', groupEid) const helpers = serverHelpers({}) diff --git a/frontend/apps/site/pages/g/[groupId]/index.tsx b/frontend/apps/site/pages/g/[groupEid]/index.tsx similarity index 100% rename from frontend/apps/site/pages/g/[groupId]/index.tsx rename to frontend/apps/site/pages/g/[groupEid]/index.tsx diff --git a/frontend/apps/site/publication-metadata.tsx b/frontend/apps/site/publication-metadata.tsx index 9c013f4650..173148346e 100644 --- a/frontend/apps/site/publication-metadata.tsx +++ b/frontend/apps/site/publication-metadata.tsx @@ -574,31 +574,31 @@ export function PublishedMeta({ publication?: HMPublication | null pathName?: string }) { - const pathRecord = trpc.publication.getPath.useQuery( - {pathName}, - {enabled: !!pathName}, - ) + // const pathRecord = trpc.publication.getPath.useQuery( + // {pathName}, + // {enabled: !!pathName}, + // ) const publishTime = publication?.document?.publishTime const publishTimeRelative = useFormattedTime(publishTime, true) const publishTimeDate = publishTime && new Date(publishTime) let latestVersion: null | ReactElement = null - const {documentId: pathDocId, versionId: pathVersionId} = - pathRecord.data || {} - if ( - pathName && - pathRecord.data && - pathRecord && - publication && - pathDocId && - pathVersionId && - (pathDocId !== publication.document?.id || - pathVersionId !== publication.version) - ) { - latestVersion = ( - - ) - } + // const {documentId: pathDocId, versionId: pathVersionId} = + // pathRecord.data || {} + // if ( + // pathName && + // pathRecord.data && + // pathRecord && + // publication && + // pathDocId && + // pathVersionId && + // (pathDocId !== publication.document?.id || + // pathVersionId !== publication.version) + // ) { + // latestVersion = ( + // + // ) + // } return ( diff --git a/frontend/apps/site/server/json-hm.ts b/frontend/apps/site/server/json-hm.ts index c7311db7ac..f02e1644b2 100644 --- a/frontend/apps/site/server/json-hm.ts +++ b/frontend/apps/site/server/json-hm.ts @@ -11,7 +11,7 @@ import type { MttLink, Profile, Publication, - SiteInfo, + Group_SiteInfo, } from '@mintter/shared' export type ServerChangeInfo = ChangeInfo @@ -68,12 +68,12 @@ export type HMPublication = { version?: string } -export type ServerSiteInfo = SiteInfo -export type HMSiteInfo = { - hostname?: string - title?: string - description?: string - owner?: string +export type ServerGroupSiteInfo = Group_SiteInfo +export type HMGroupSiteInfo = { + baseUrl?: string + lastSyncTime?: HMTimestamp + lastOkSyncTime?: HMTimestamp + version?: string } export type ServerDevice = Device diff --git a/frontend/apps/site/server/page-slug.ts b/frontend/apps/site/server/page-slug.ts index cc073d93a4..b11f618791 100644 --- a/frontend/apps/site/server/page-slug.ts +++ b/frontend/apps/site/server/page-slug.ts @@ -21,13 +21,13 @@ export async function prepareSlugPage( peerInfo.addrs.join(','), ) - const path = await helpers.publication.getPath.fetch({pathName}) + // const path = await helpers.publication.getPath.fetch({pathName}) - if (!path) return {notFound: true} as const - await helpers.publication.get.prefetch({ - documentId: path.documentId, - versionId: path.versionId, - }) + // if (!path) return {notFound: true} as const + // await helpers.publication.get.prefetch({ + // documentId: path.documentId, + // versionId: path.versionId, + // }) return { props: await getPageProps(helpers, {pathName}), } diff --git a/frontend/apps/site/server/routers/_app.ts b/frontend/apps/site/server/routers/_app.ts index 5c05b0b256..55f2ab32f4 100644 --- a/frontend/apps/site/server/routers/_app.ts +++ b/frontend/apps/site/server/routers/_app.ts @@ -3,13 +3,12 @@ import { Accounts, Changes, ContentGraph, + Entities, Groups, Publications, - WebPublishing, unpackDocId, } from '@mintter/shared' -import {HMChangeInfo} from '@mintter/ui' -import {localWebsiteClient, transport} from 'client' +import {transport} from 'client' import {getSiteInfo} from 'get-site-info' import { hmAccount, @@ -17,17 +16,17 @@ import { hmGroup, hmLink, hmPublication, - hmSiteInfo, } from 'server/to-json-hm' +import {HMChangeInfo} from 'server/json-hm' import {z} from 'zod' import {procedure, router} from '../trpc' const contentGraphClient = createPromiseClient(ContentGraph, transport) const publicationsClient = createPromiseClient(Publications, transport) -const webClient = createPromiseClient(WebPublishing, transport) const accountsClient = createPromiseClient(Accounts, transport) const groupsClient = createPromiseClient(Groups, transport) const changesClient = createPromiseClient(Changes, transport) +const entitiesClient = createPromiseClient(Entities, transport) const publicationRouter = router({ getPathInfo: procedure @@ -38,52 +37,14 @@ const publicationRouter = router({ }), ) .query(async ({input}) => { - const records = await webClient.listWebPublicationRecords({ - documentId: input.documentId, - version: input.versionId, - }) - return { - webPublications: records.publications, - } - }), - getPath: procedure - .input( - z.object({ - pathName: z.string().optional(), - }), - ) - .query(async ({input}) => { - if (!input.pathName) return null - const pathRecord = await localWebsiteClient.getPath({ - path: input.pathName, - }) - const publication = pathRecord?.publication - const documentId = publication?.document?.id - if (!publication || !documentId) return null - return { - versionId: publication.version, - documentId, - publishTime: publication.document?.publishTime?.toJson() as string, - } - }), - getDocRecord: procedure - .input( - z.object({ - documentId: z.string().optional(), - }), - ) - .query(async ({input}) => { - if (!input.documentId) return null - const webPubs = await localWebsiteClient.listWebPublications({}) - const pub = webPubs.publications.find( - (pub) => pub.documentId === input.documentId, - ) - if (!pub) return null - return { - path: pub.path, - versionId: pub.version, - documentId: pub.documentId, - } + // const records = await webClient.listWebPublicationRecords({ + // documentId: input.documentId, + // version: input.versionId, + // }) + // return { + // webPublications: records.publications, + // } + return null }), get: procedure .input( @@ -96,12 +57,32 @@ const publicationRouter = router({ if (!input.documentId) { return {publication: null} } - const pub = await publicationsClient.getPublication({ - documentId: input.documentId, - version: input.versionId || '', - }) + const alreadyPub = publicationsClient + .getPublication({ + documentId: input.documentId, + version: input.versionId || '', + }) + .catch((e) => undefined) + const remoteSync = entitiesClient + .discoverEntity({ + id: input.documentId, + version: input.versionId || '', + }) + .then(() => undefined) + let resolvedPub = await Promise.race([alreadyPub, remoteSync]) + if (!resolvedPub) { + // the remote Sync may have just found it. so we want to re-fetch + resolvedPub = await publicationsClient.getPublication({ + documentId: input.documentId, + version: input.versionId || '', + }) + } + if (!resolvedPub) { + return {publication: null} + } + return {publication: null} return { - publication: hmPublication(pub), + publication: hmPublication(resolvedPub) || null, } }), getEmbedMeta: procedure @@ -230,14 +211,14 @@ const groupRouter = router({ .query(async ({input}) => { // todo. get current group content and find the pathName, return the corresponding doc console.log('getting site info') - const siteInfo = await groupsClient.getSiteInfo({ - hostname: input.hostname, - }) - return { - groupId: siteInfo.groupId, - ownerId: siteInfo.ownerId, - version: siteInfo.version, - } + // const siteInfo = await groupsClient.getGroup({ + // hostname: input.hostname, + // }) + // return { + // groupId: siteInfo.groupId, + // ownerId: siteInfo.ownerId, + // version: siteInfo.version, + // } }), getGroupPath: procedure .input( @@ -362,8 +343,8 @@ const accountRouter = router({ const siteInfoRouter = router({ get: procedure.query(async () => { - const siteInfo = await getSiteInfo() - return hmSiteInfo(siteInfo) + // const siteInfo = await getSiteInfo() + return null }), }) diff --git a/frontend/apps/site/server/ssr-helpers.ts b/frontend/apps/site/server/ssr-helpers.ts index 874659417f..fa8df3ace4 100644 --- a/frontend/apps/site/server/ssr-helpers.ts +++ b/frontend/apps/site/server/ssr-helpers.ts @@ -15,7 +15,7 @@ export async function getPageProps( helpers: ServerHelpers, props: AdditionalProps, ): Promise { - await helpers.siteInfo.get.prefetch() + // await helpers.siteInfo.get.prefetch() return { ...props, trpcState: helpers.dehydrate(), diff --git a/frontend/apps/site/server/to-json-hm.ts b/frontend/apps/site/server/to-json-hm.ts index 204be61d4b..0520441c64 100644 --- a/frontend/apps/site/server/to-json-hm.ts +++ b/frontend/apps/site/server/to-json-hm.ts @@ -1,30 +1,19 @@ -import { - Account, - ChangeInfo, - Group, - MttLink, - Publication, - SiteInfo, -} from '@mintter/shared' +import {Account, ChangeInfo, MttLink, Publication, Group} from '@mintter/shared' import { HMAccount, HMChangeInfo, - HMGroup, - HMLink, HMPublication, - HMSiteInfo, + HMLink, + HMGroup, } from './json-hm' -export function hmPublication(input?: Publication | null) { +export function hmPublication( + input?: Publication | null, +): HMPublication | null { if (!input) return null return input.toJson() as HMPublication } -export function hmSiteInfo(input?: SiteInfo | null) { - if (!input) return null - return input.toJson() as HMSiteInfo -} - export function hmAccount(input?: Account | null) { if (!input) return null return input.toJson() as HMAccount diff --git a/frontend/apps/site/trpc.ts b/frontend/apps/site/trpc.ts index 0a17315a77..9946f4a29c 100644 --- a/frontend/apps/site/trpc.ts +++ b/frontend/apps/site/trpc.ts @@ -7,7 +7,7 @@ function getBaseUrl() { if (typeof window !== 'undefined') // browser should use relative path return '' - if (process.env.GW_NEXT_HOST) return process.env.GW_NEXT_HOST + if (process.env.HM_BASE_URL) return process.env.HM_BASE_URL // assume localhost return `http://localhost:${process.env.PORT ?? 3000}` } diff --git a/frontend/packages/app/src/models/groups.ts b/frontend/packages/app/src/models/groups.ts index eb5f1c27be..be792933d0 100644 --- a/frontend/packages/app/src/models/groups.ts +++ b/frontend/packages/app/src/models/groups.ts @@ -13,7 +13,7 @@ import {useGRPCClient, useQueryInvalidator} from '../app-context' import {useMyAccount} from './accounts' import {queryKeys} from './query-keys' -export function useGroups(opts: UseQueryOptions) { +export function useGroups(opts?: UseQueryOptions) { const grpcClient = useGRPCClient() return useQuery({ ...opts, @@ -101,9 +101,9 @@ export function usePublishGroupToSite( groupId, setupUrl, }: PublishGroupToSiteMutationInput) => { - await grpcClient.groups.convertToSite({ - link: setupUrl, - groupId, + await grpcClient.groups.updateGroup({ + siteSetupUrl: setupUrl, + id: groupId, }) }, onSuccess: (result, input, context) => { @@ -286,9 +286,9 @@ export function useHostGroup(hostname: string) { return useQuery({ queryKey: [queryKeys.GET_HOST_GROUP, hostname], queryFn: async () => { - return await grpcClient.groups.getSiteInfo({ - hostname, - }) + // return await grpcClient.groups.({ + // hostname, + // }) }, }) } diff --git a/frontend/packages/app/src/pages/group.tsx b/frontend/packages/app/src/pages/group.tsx index 9970c8cac5..ad61dad16d 100644 --- a/frontend/packages/app/src/pages/group.tsx +++ b/frontend/packages/app/src/pages/group.tsx @@ -10,6 +10,7 @@ import { } from '@mintter/shared' import { Button, + ButtonText, Container, DialogDescription, DialogTitle, @@ -57,6 +58,8 @@ import {GroupRoute, useNavRoute} from '../utils/navigation' import {useOpenDraft} from '../utils/open-draft' import {pathNameify} from '../utils/path' import {useNavigate} from '../utils/useNavigate' +import {Allotment} from 'allotment' +import {useOpenUrl} from '../open-url' export function RenamePubDialog({ input: {groupId, pathName, docTitle}, @@ -276,6 +279,7 @@ export default function GroupPage() { : undefined const frontPageId = frontDocumentUrl ? unpackDocId(frontDocumentUrl) : null const memberCount = Object.keys(groupMembers.data?.members || {}).length + const siteBaseUrl = group.data?.siteInfo?.baseUrl const editGroupInfo = useEditGroupInfoDialog() const removeDoc = useRemoveDocFromGroup() const frontDocMenuItems = [ @@ -298,6 +302,7 @@ export default function GroupPage() { } : null, ].filter(Boolean) + const openUrl = useOpenUrl() return ( <> @@ -323,7 +328,22 @@ export default function GroupPage() { ) : null} - + + {siteBaseUrl && ( + + { + openUrl(siteBaseUrl) + }} + color="$blue10" + > + {siteBaseUrl} + + + )} {!frontDocumentUrl && isMember && (