mirror of
https://github.com/anyproto/anytype-heart.git
synced 2025-06-07 21:37:04 +09:00
Go 2450 subscriptions (#1041)
* GO-2450 Add Payments commands * GO-2450 Payment methods: remove IDs from requests * GO-2450 Use GetAccountEthAddress() * GO-2450 Use any-sync instead of pp repo * GO-2450 Add RequestedAnyName field * GO-2450 Basic nameservice methods * GO-2450 New methods for subscriptions/nameservice * GO-2450 Refactor: protos for payments * GO-2450 Downgrade go to 1.20 * GO-2450 Fix build * GO-2450 Refactoring: renames * GO-2450 GetPortalURL implemented; Test tiers * GO-2450 Update any-sync * GO-2450 Fix: bootstrap * GO-2450 Fix pp encryption: peer key -> sign key * GO-2450 Bug fix: Ethereum wallet address * GO-2450 Update tier names * GO-2450 Email verification methods * GO-2450 Return email if was verified before * GO-2450 Update any-sync * GO-2450 Update any-sync * GO-2395 WiP: cache for PP node * GO-2395: cache for PP node * GO-2395 Change logics: return 0 tier when no response from the pp * GO-2395 fix: cache logics * GO-2450 Use any-sync from feature-payments branch for now * GO-2395 any-sync update * GO-2395 Fixes after review * GO-2395 Refactoring after review * GO-2395 Review fixes * GO-2395 Build fix * GO-2450 Refactoring: payments interface; tier -> int32 * GO-2450 Add FinalizeSubscription method * GO-2450 Cache fix * GO-2450 GetSubscriptionStatus: add NoCache * GO-2734 Add global name to cache WIP * GO-2450 go mod tidy * GO-2450 Update any-sync * GO-2450 Linter fix * GO-2450 PaymentsTiersGet * GO-2450 Refactoring: PaymentsGetTiers * GO-2450 NS: implement NameServiceResolveAnyId * GO-2734 Use AnyId to retrieve Global name * GO-2734 Add GlobalName to identity * GO-2734 Implement DetailsSettable in participant * GO-2734 Get GlobalName from NN only on app start * GO-2450 Upgrade any-sync to v0.3.33: TODOs in the required methods * GO-2734 Fix tests WIP * GO-2734 Use batch method * GO-2734 Fix unittest * GO-3061 Refactoring: payments - huge renames * GO-3061 Add EventMembershipUpdate * GO-2734 Fix tests 2 * GO-2734 Fixes upon pr comments * GO-2450 Fix panic with nil cache.data * GO-2450 Fixes after merge * GO-2734 Fix unittests WIP * GO-2734 Move mock expectations to newFixture * GO-2734 Add return statement in mock * GO-2734 Add check if name was found * GO-2450 Add IsNameValid method * GO-2450 Fix tests: cache * GO-2734 Resolve names directly from NS * GO-2450 Cache logics fix * GO-2450 refactoring: cache logics simplified * GO-2450 refactoring: IsNameValid - code -> error * GO-2734 Set globalName in new spaces * GO-3128 IsNameValid, GetAllTiers rebuilt * GO-2734 Rename forceUpdate flag * GO-2734 Save globalName even if is not found * GO-3128 IsNameValid, GetAllTiers rebuilt * GO-3128 Fix string len * GO-2734 Rename UpdateIdentities --------- Co-authored-by: kirillston <stonozhenko@anytype.io> Co-authored-by: Kirill Stonozhenko <40611691+KirillSto@users.noreply.github.com>
This commit is contained in:
parent
c127cd5426
commit
bf46949207
30 changed files with 20564 additions and 2370 deletions
|
@ -91,3 +91,6 @@ packages:
|
|||
github.com/anyproto/anytype-heart/core/files/fileacl:
|
||||
interfaces:
|
||||
Service:
|
||||
github.com/anyproto/anytype-heart/core/payments/cache:
|
||||
interfaces:
|
||||
CacheService:
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -63,6 +63,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/core/invitestore"
|
||||
"github.com/anyproto/anytype-heart/core/kanban"
|
||||
"github.com/anyproto/anytype-heart/core/notifications"
|
||||
"github.com/anyproto/anytype-heart/core/payments"
|
||||
"github.com/anyproto/anytype-heart/core/recordsbatcher"
|
||||
"github.com/anyproto/anytype-heart/core/subscription"
|
||||
"github.com/anyproto/anytype-heart/core/syncstatus"
|
||||
|
@ -94,6 +95,11 @@ import (
|
|||
"github.com/anyproto/anytype-heart/util/linkpreview"
|
||||
"github.com/anyproto/anytype-heart/util/unsplash"
|
||||
"github.com/anyproto/anytype-heart/util/vcs"
|
||||
|
||||
"github.com/anyproto/any-sync/nameservice/nameserviceclient"
|
||||
"github.com/anyproto/any-sync/paymentservice/paymentserviceclient"
|
||||
|
||||
paymentscache "github.com/anyproto/anytype-heart/core/payments/cache"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -270,7 +276,11 @@ func Bootstrap(a *app.App, components ...app.Component) {
|
|||
Register(profiler.New()).
|
||||
Register(identity.New(30*time.Second, 10*time.Second)).
|
||||
Register(templateservice.New()).
|
||||
Register(notifications.New())
|
||||
Register(notifications.New()).
|
||||
Register(paymentserviceclient.New()).
|
||||
Register(nameserviceclient.New()).
|
||||
Register(payments.New()).
|
||||
Register(paymentscache.New())
|
||||
}
|
||||
|
||||
func MiddlewareVersion() string {
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/identityrepo/identityrepoproto"
|
||||
"github.com/anyproto/any-sync/nameservice/nameserviceclient"
|
||||
"github.com/anyproto/any-sync/nameservice/nameserviceproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
|
@ -17,6 +19,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/core/anytype/account"
|
||||
"github.com/anyproto/anytype-heart/core/domain"
|
||||
"github.com/anyproto/anytype-heart/core/files/fileacl"
|
||||
"github.com/anyproto/anytype-heart/core/wallet"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
coresb "github.com/anyproto/anytype-heart/pkg/lib/core/smartblock"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/database"
|
||||
|
@ -39,6 +42,8 @@ var (
|
|||
type Service interface {
|
||||
GetMyProfileDetails() (identity string, metadataKey crypto.SymKey, details *types.Struct)
|
||||
|
||||
UpdateGlobalNames()
|
||||
|
||||
RegisterIdentity(spaceId string, identity string, encryptionKey crypto.SymKey, observer func(identity string, profile *model.IdentityProfile)) error
|
||||
|
||||
// UnregisterIdentity removes the observer for the identity in specified space
|
||||
|
@ -73,6 +78,8 @@ type service struct {
|
|||
spaceIdDeriver spaceIdDeriver
|
||||
identityRepoClient identityRepoClient
|
||||
fileAclService fileacl.Service
|
||||
wallet wallet.Wallet
|
||||
namingService nameserviceclient.AnyNsClientService
|
||||
closing chan struct{}
|
||||
startedCh chan struct{}
|
||||
techSpaceId string
|
||||
|
@ -84,13 +91,15 @@ type service struct {
|
|||
pushIdentityTimer *time.Timer // timer for batching
|
||||
pushIdentityBatchTimeout time.Duration
|
||||
|
||||
identityObservePeriod time.Duration
|
||||
identityForceUpdate chan struct{}
|
||||
lock sync.RWMutex
|
||||
identityObservePeriod time.Duration
|
||||
identityForceUpdate chan struct{}
|
||||
globalNamesForceUpdate chan struct{}
|
||||
lock sync.RWMutex
|
||||
// identity => spaceId => observer
|
||||
identityObservers map[string]map[string]*observer
|
||||
identityEncryptionKeys map[string]crypto.SymKey
|
||||
identityProfileCache map[string]*model.IdentityProfile
|
||||
identityGlobalNames map[string]*nameserviceproto.NameByAddressResponse
|
||||
}
|
||||
|
||||
func New(identityObservePeriod time.Duration, pushIdentityBatchTimeout time.Duration) Service {
|
||||
|
@ -98,10 +107,12 @@ func New(identityObservePeriod time.Duration, pushIdentityBatchTimeout time.Dura
|
|||
startedCh: make(chan struct{}),
|
||||
closing: make(chan struct{}),
|
||||
identityForceUpdate: make(chan struct{}),
|
||||
globalNamesForceUpdate: make(chan struct{}),
|
||||
identityObservePeriod: identityObservePeriod,
|
||||
identityObservers: make(map[string]map[string]*observer),
|
||||
identityEncryptionKeys: make(map[string]crypto.SymKey),
|
||||
identityProfileCache: make(map[string]*model.IdentityProfile),
|
||||
identityGlobalNames: make(map[string]*nameserviceproto.NameByAddressResponse),
|
||||
pushIdentityBatchTimeout: pushIdentityBatchTimeout,
|
||||
}
|
||||
}
|
||||
|
@ -114,6 +125,8 @@ func (s *service) Init(a *app.App) (err error) {
|
|||
s.identityRepoClient = app.MustComponent[identityRepoClient](a)
|
||||
s.fileAclService = app.MustComponent[fileacl.Service](a)
|
||||
s.dbProvider = app.MustComponent[datastore.Datastore](a)
|
||||
s.wallet = app.MustComponent[wallet.Wallet](a)
|
||||
s.namingService = app.MustComponent[nameserviceclient.AnyNsClientService](a)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -218,6 +231,13 @@ func (s *service) GetMyProfileDetails() (identity string, metadataKey crypto.Sym
|
|||
return s.myIdentity, s.spaceService.AccountMetadataSymKey(), s.currentProfileDetails
|
||||
}
|
||||
|
||||
func (s *service) UpdateGlobalNames() {
|
||||
select {
|
||||
case s.globalNamesForceUpdate <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) WaitProfile(ctx context.Context, identity string) *model.IdentityProfile {
|
||||
profile := s.getProfileFromCache(identity)
|
||||
if profile != nil {
|
||||
|
@ -282,6 +302,7 @@ func (s *service) cacheProfileDetails(details *types.Struct) {
|
|||
Name: pbtypes.GetString(details, bundle.RelationKeyName.String()),
|
||||
Description: pbtypes.GetString(details, bundle.RelationKeyDescription.String()),
|
||||
IconCid: pbtypes.GetString(details, bundle.RelationKeyIconImage.String()),
|
||||
GlobalName: pbtypes.GetString(details, bundle.RelationKeyGlobalName.String()),
|
||||
}
|
||||
|
||||
s.lock.RLock()
|
||||
|
@ -318,6 +339,7 @@ func (s *service) pushProfileToIdentityRegistry(ctx context.Context) error {
|
|||
Description: pbtypes.GetString(s.currentProfileDetails, bundle.RelationKeyDescription.String()),
|
||||
IconCid: iconCid,
|
||||
IconEncryptionKeys: iconEncryptionKeys,
|
||||
GlobalName: pbtypes.GetString(s.currentProfileDetails, bundle.RelationKeyGlobalName.String()),
|
||||
}
|
||||
data, err := proto.Marshal(identityProfile)
|
||||
if err != nil {
|
||||
|
@ -352,8 +374,8 @@ func (s *service) observeIdentitiesLoop() {
|
|||
defer ticker.Stop()
|
||||
|
||||
ctx := context.Background()
|
||||
observe := func() {
|
||||
err := s.observeIdentities(ctx)
|
||||
observe := func(globalNamesForceUpdate bool) {
|
||||
err := s.observeIdentities(ctx, globalNamesForceUpdate)
|
||||
if err != nil {
|
||||
log.Error("error observing identities", zap.Error(err))
|
||||
}
|
||||
|
@ -363,9 +385,11 @@ func (s *service) observeIdentitiesLoop() {
|
|||
case <-s.closing:
|
||||
return
|
||||
case <-s.identityForceUpdate:
|
||||
observe()
|
||||
observe(false)
|
||||
case <-s.globalNamesForceUpdate:
|
||||
observe(true)
|
||||
case <-ticker.C:
|
||||
observe()
|
||||
observe(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -373,7 +397,7 @@ func (s *service) observeIdentitiesLoop() {
|
|||
const identityRepoDataKind = "profile"
|
||||
|
||||
// TODO Maybe we need to use backoff in case of error from coordinator
|
||||
func (s *service) observeIdentities(ctx context.Context) error {
|
||||
func (s *service) observeIdentities(ctx context.Context, globalNamesForceUpdate bool) error {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
|
@ -392,6 +416,10 @@ func (s *service) observeIdentities(ctx context.Context) error {
|
|||
return fmt.Errorf("failed to pull identity: %w", err)
|
||||
}
|
||||
|
||||
if err = s.fetchGlobalNames(append(identities, s.myIdentity), globalNamesForceUpdate); err != nil {
|
||||
log.Error("error fetching identities global names from Naming Service", zap.Error(err))
|
||||
}
|
||||
|
||||
for _, identityData := range identitiesData {
|
||||
err := s.broadcastIdentityProfile(identityData)
|
||||
if err != nil {
|
||||
|
@ -449,6 +477,10 @@ func (s *service) broadcastIdentityProfile(identityData *identityrepoproto.DataW
|
|||
return fmt.Errorf("find profile: %w", err)
|
||||
}
|
||||
|
||||
if globalName, found := s.identityGlobalNames[identityData.Identity]; found && globalName.Found {
|
||||
profile.GlobalName = globalName.Name
|
||||
}
|
||||
|
||||
prevProfile, ok := s.identityProfileCache[identityData.Identity]
|
||||
hasUpdates := !ok || !proto.Equal(prevProfile, profile)
|
||||
|
||||
|
@ -498,6 +530,32 @@ func (s *service) findProfile(identityData *identityrepoproto.DataWithIdentity)
|
|||
return profile, rawProfile, nil
|
||||
}
|
||||
|
||||
func (s *service) fetchGlobalNames(identities []string, forceUpdate bool) error {
|
||||
if len(s.identityGlobalNames) == len(identities) && !forceUpdate {
|
||||
return nil
|
||||
}
|
||||
response, err := s.namingService.BatchGetNameByAnyId(context.Background(), &nameserviceproto.BatchNameByAnyIdRequest{AnyAddresses: identities})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if response == nil {
|
||||
return nil
|
||||
}
|
||||
for i, anyID := range identities {
|
||||
s.identityGlobalNames[anyID] = response.Results[i]
|
||||
if anyID == s.myIdentity && response.Results[i].Found {
|
||||
s.currentProfileDetailsLock.RLock()
|
||||
details := pbtypes.CopyStruct(s.currentProfileDetails)
|
||||
s.currentProfileDetailsLock.RUnlock()
|
||||
details.Fields[bundle.RelationKeyGlobalName.String()] = pbtypes.String(response.Results[i].Name)
|
||||
if err = s.objectStore.UpdateObjectDetails(pbtypes.GetString(details, bundle.RelationKeyId.String()), details); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) cacheIdentityProfile(rawProfile []byte, profile *model.IdentityProfile) error {
|
||||
s.identityProfileCache[profile.Identity] = profile
|
||||
return badgerhelper.SetValue(s.db, makeIdentityProfileKey(profile.Identity), rawProfile)
|
||||
|
|
|
@ -9,13 +9,18 @@ import (
|
|||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/identityrepo/identityrepoproto"
|
||||
mock_nameserviceclient "github.com/anyproto/any-sync/nameservice/nameserviceclient/mock"
|
||||
"github.com/anyproto/any-sync/nameservice/nameserviceproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/anytype/account/mock_account"
|
||||
"github.com/anyproto/anytype-heart/core/files/fileacl/mock_fileacl"
|
||||
"github.com/anyproto/anytype-heart/core/wallet/mock_wallet"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/datastore"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
|
@ -29,10 +34,15 @@ type fixture struct {
|
|||
coordinatorClient *inMemoryIdentityRepo
|
||||
}
|
||||
|
||||
const testObserverPeriod = 1 * time.Millisecond
|
||||
const (
|
||||
testObserverPeriod = 1 * time.Millisecond
|
||||
globalName = "anytypeuser.any"
|
||||
identity = "identity1"
|
||||
)
|
||||
|
||||
func newFixture(t *testing.T) *fixture {
|
||||
ctx := context.Background()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
identityRepoClient := newInMemoryIdentityRepo()
|
||||
objectStore := objectstore.NewStoreFixture(t)
|
||||
|
@ -40,6 +50,17 @@ func newFixture(t *testing.T) *fixture {
|
|||
spaceService := mock_space.NewMockService(t)
|
||||
fileAclService := mock_fileacl.NewMockService(t)
|
||||
dataStore := datastore.NewInMemory()
|
||||
wallet := mock_wallet.NewMockWallet(t)
|
||||
nsClient := mock_nameserviceclient.NewMockAnyNsClientService(ctrl)
|
||||
nsClient.EXPECT().BatchGetNameByAnyId(gomock.Any(), &nameserviceproto.BatchNameByAnyIdRequest{AnyAddresses: []string{identity, ""}}).AnyTimes().
|
||||
Return(&nameserviceproto.BatchNameByAddressResponse{Results: []*nameserviceproto.NameByAddressResponse{{
|
||||
Found: true,
|
||||
Name: globalName,
|
||||
}, {
|
||||
Found: false,
|
||||
Name: "",
|
||||
},
|
||||
}}, nil)
|
||||
err := dataStore.Run(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -51,6 +72,8 @@ func newFixture(t *testing.T) *fixture {
|
|||
a.Register(testutil.PrepareMock(ctx, a, accountService))
|
||||
a.Register(testutil.PrepareMock(ctx, a, spaceService))
|
||||
a.Register(testutil.PrepareMock(ctx, a, fileAclService))
|
||||
a.Register(testutil.PrepareMock(ctx, a, wallet))
|
||||
a.Register(testutil.PrepareMock(ctx, a, nsClient))
|
||||
|
||||
svc := New(testObserverPeriod, 1*time.Microsecond)
|
||||
err = svc.Init(a)
|
||||
|
@ -63,6 +86,7 @@ func newFixture(t *testing.T) *fixture {
|
|||
db, err := dataStore.LocalStorage()
|
||||
require.NoError(t, err)
|
||||
svcRef.db = db
|
||||
svcRef.currentProfileDetails = &types.Struct{Fields: make(map[string]*types.Value)}
|
||||
fx := &fixture{
|
||||
service: svcRef,
|
||||
coordinatorClient: identityRepoClient,
|
||||
|
@ -145,8 +169,9 @@ func TestIdentityProfileCache(t *testing.T) {
|
|||
profileSymKey, err := crypto.NewRandomAES()
|
||||
require.NoError(t, err)
|
||||
wantProfile := &model.IdentityProfile{
|
||||
Identity: identity,
|
||||
Name: "name1",
|
||||
Identity: identity,
|
||||
Name: "name1",
|
||||
GlobalName: globalName,
|
||||
}
|
||||
wantData := marshalProfile(t, wantProfile, profileSymKey)
|
||||
|
||||
|
@ -174,8 +199,9 @@ func TestObservers(t *testing.T) {
|
|||
profileSymKey, err := crypto.NewRandomAES()
|
||||
require.NoError(t, err)
|
||||
wantProfile := &model.IdentityProfile{
|
||||
Identity: identity,
|
||||
Name: "name1",
|
||||
Identity: identity,
|
||||
Name: "name1",
|
||||
GlobalName: globalName,
|
||||
}
|
||||
wantData := marshalProfile(t, wantProfile, profileSymKey)
|
||||
|
||||
|
@ -203,6 +229,7 @@ func TestObservers(t *testing.T) {
|
|||
Identity: identity,
|
||||
Name: "name1 edited",
|
||||
Description: "my description",
|
||||
GlobalName: globalName,
|
||||
}
|
||||
wantData2 := marshalProfile(t, wantProfile2, profileSymKey)
|
||||
|
||||
|
@ -220,13 +247,15 @@ func TestObservers(t *testing.T) {
|
|||
|
||||
wantCalls := []*model.IdentityProfile{
|
||||
{
|
||||
Identity: identity,
|
||||
Name: "name1",
|
||||
Identity: identity,
|
||||
Name: "name1",
|
||||
GlobalName: globalName,
|
||||
},
|
||||
{
|
||||
Identity: identity,
|
||||
Name: "name1 edited",
|
||||
Description: "my description",
|
||||
GlobalName: globalName,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, wantCalls, callbackCalls)
|
||||
|
|
137
core/nameservice.go
Normal file
137
core/nameservice.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anyproto/any-sync/nameservice/nameserviceclient"
|
||||
proto "github.com/anyproto/any-sync/nameservice/nameserviceproto"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/wallet"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
)
|
||||
|
||||
// NameServiceResolveName does a name lookup: somename.any -> info
|
||||
func (mw *Middleware) NameServiceResolveName(ctx context.Context, req *pb.RpcNameServiceResolveNameRequest) *pb.RpcNameServiceResolveNameResponse {
|
||||
ns := getService[nameserviceclient.AnyNsClientService](mw)
|
||||
|
||||
var in proto.NameAvailableRequest
|
||||
in.FullName = req.FullName
|
||||
|
||||
nar, err := ns.IsNameAvailable(ctx, &in)
|
||||
if err != nil {
|
||||
return &pb.RpcNameServiceResolveNameResponse{
|
||||
Error: &pb.RpcNameServiceResolveNameResponseError{
|
||||
// we don't map error codes here
|
||||
Code: pb.RpcNameServiceResolveNameResponseError_UNKNOWN_ERROR,
|
||||
Description: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Return the response
|
||||
var out pb.RpcNameServiceResolveNameResponse
|
||||
out.Available = nar.Available
|
||||
out.OwnerAnyAddress = nar.OwnerAnyAddress
|
||||
// EOA is onwer of -> SCW is owner of -> name
|
||||
out.OwnerEthAddress = nar.OwnerEthAddress
|
||||
out.OwnerScwEthAddress = nar.OwnerScwEthAddress
|
||||
out.SpaceId = nar.SpaceId
|
||||
out.NameExpires = nar.NameExpires
|
||||
|
||||
return &out
|
||||
}
|
||||
|
||||
func (mw *Middleware) NameServiceResolveAnyId(ctx context.Context, req *pb.RpcNameServiceResolveAnyIdRequest) *pb.RpcNameServiceResolveAnyIdResponse {
|
||||
// Get name service object that connects to the remote "namingNode"
|
||||
// in order for that to work, we need to have a "namingNode" node in the nodes section of the config
|
||||
ns := getService[nameserviceclient.AnyNsClientService](mw)
|
||||
|
||||
var in proto.NameByAnyIdRequest
|
||||
in.AnyAddress = req.AnyId
|
||||
|
||||
nar, err := ns.GetNameByAnyId(ctx, &in)
|
||||
if err != nil {
|
||||
return &pb.RpcNameServiceResolveAnyIdResponse{
|
||||
Error: &pb.RpcNameServiceResolveAnyIdResponseError{
|
||||
// we don't map error codes here
|
||||
Code: pb.RpcNameServiceResolveAnyIdResponseError_UNKNOWN_ERROR,
|
||||
Description: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Return the response
|
||||
var out pb.RpcNameServiceResolveAnyIdResponse
|
||||
out.Found = nar.Found
|
||||
out.FullName = nar.Name
|
||||
|
||||
return &out
|
||||
}
|
||||
|
||||
func (mw *Middleware) NameServiceResolveSpaceId(ctx context.Context, req *pb.RpcNameServiceResolveSpaceIdRequest) *pb.RpcNameServiceResolveSpaceIdResponse {
|
||||
// TODO: implement
|
||||
// TODO: test
|
||||
|
||||
return &pb.RpcNameServiceResolveSpaceIdResponse{
|
||||
Error: &pb.RpcNameServiceResolveSpaceIdResponseError{
|
||||
Code: pb.RpcNameServiceResolveSpaceIdResponseError_UNKNOWN_ERROR,
|
||||
Description: "not implemented",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (mw *Middleware) NameServiceUserAccountGet(ctx context.Context, req *pb.RpcNameServiceUserAccountGetRequest) *pb.RpcNameServiceUserAccountGetResponse {
|
||||
// 1 - get name service object that connects to the remote "namingNode"
|
||||
// in order for that to work, we need to have a "namingNode" node in the nodes section of the config
|
||||
ns := getService[nameserviceclient.AnyNsClientService](mw)
|
||||
|
||||
// 2 - get user's ETH address from the wallet
|
||||
w := getService[wallet.Wallet](mw)
|
||||
|
||||
// 3 - get user's account info
|
||||
//
|
||||
// when AccountAbstraction is used to deploy a smart contract wallet
|
||||
// then name is really owned by this SCW, but owner of this SCW is
|
||||
// EOA that was used to sign transaction
|
||||
//
|
||||
// EOA (w.GetAccountEthAddress()) -> SCW (ua.OwnerSmartContracWalletAddress) -> name
|
||||
var guar proto.GetUserAccountRequest
|
||||
guar.OwnerEthAddress = w.GetAccountEthAddress().Hex()
|
||||
|
||||
ua, err := ns.GetUserAccount(ctx, &guar)
|
||||
if err != nil {
|
||||
return &pb.RpcNameServiceUserAccountGetResponse{
|
||||
Error: &pb.RpcNameServiceUserAccountGetResponseError{
|
||||
Code: pb.RpcNameServiceUserAccountGetResponseError_UNKNOWN_ERROR,
|
||||
Description: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 4 - check if any name is attached to the account (reverse resolve the name)
|
||||
var in proto.NameByAddressRequest
|
||||
|
||||
// NOTE: we are passing here SCW address, not initial ETH address!
|
||||
// read comment about SCW above please
|
||||
in.OwnerScwEthAddress = ua.OwnerSmartContracWalletAddress
|
||||
|
||||
nar, err := ns.GetNameByAddress(ctx, &in)
|
||||
if err != nil {
|
||||
return &pb.RpcNameServiceUserAccountGetResponse{
|
||||
Error: &pb.RpcNameServiceUserAccountGetResponseError{
|
||||
// we don't map error codes here
|
||||
Code: pb.RpcNameServiceUserAccountGetResponseError_BAD_NAME_RESOLVE,
|
||||
Description: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Return the response
|
||||
var out pb.RpcNameServiceUserAccountGetResponse
|
||||
out.NamesCountLeft = ua.NamesCountLeft
|
||||
out.OperationsCountLeft = ua.OperationsCountLeft
|
||||
// not checking nar.Found here, no need
|
||||
out.AnyNameAttached = nar.Name
|
||||
|
||||
return &out
|
||||
}
|
144
core/payments.go
Normal file
144
core/payments.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/payments"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
)
|
||||
|
||||
func (mw *Middleware) MembershipGetStatus(ctx context.Context, req *pb.RpcMembershipGetStatusRequest) *pb.RpcMembershipGetStatusResponse {
|
||||
log.Info("payments - client asked to get a subscription status", zap.Any("req", req))
|
||||
|
||||
ps := getService[payments.Service](mw)
|
||||
out, err := ps.GetSubscriptionStatus(ctx, req)
|
||||
|
||||
if err != nil {
|
||||
return &pb.RpcMembershipGetStatusResponse{
|
||||
Error: &pb.RpcMembershipGetStatusResponseError{
|
||||
Code: pb.RpcMembershipGetStatusResponseError_UNKNOWN_ERROR,
|
||||
Description: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (mw *Middleware) MembershipIsNameValid(ctx context.Context, req *pb.RpcMembershipIsNameValidRequest) *pb.RpcMembershipIsNameValidResponse {
|
||||
ps := getService[payments.Service](mw)
|
||||
out, err := ps.IsNameValid(ctx, req)
|
||||
|
||||
// something bad has happened
|
||||
if err != nil {
|
||||
return &pb.RpcMembershipIsNameValidResponse{
|
||||
Error: &pb.RpcMembershipIsNameValidResponseError{
|
||||
Code: pb.RpcMembershipIsNameValidResponseError_UNKNOWN_ERROR,
|
||||
Description: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// out.Error will contain validation error if something is wrong with the name
|
||||
return out
|
||||
}
|
||||
|
||||
func (mw *Middleware) MembershipGetPaymentUrl(ctx context.Context, req *pb.RpcMembershipGetPaymentUrlRequest) *pb.RpcMembershipGetPaymentUrlResponse {
|
||||
ps := getService[payments.Service](mw)
|
||||
out, err := ps.GetPaymentURL(ctx, req)
|
||||
|
||||
log.Error("payments - client asked to get a payment url", zap.Any("req", req), zap.Any("out", out))
|
||||
|
||||
if err != nil {
|
||||
return &pb.RpcMembershipGetPaymentUrlResponse{
|
||||
Error: &pb.RpcMembershipGetPaymentUrlResponseError{
|
||||
Code: pb.RpcMembershipGetPaymentUrlResponseError_UNKNOWN_ERROR,
|
||||
Description: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (mw *Middleware) MembershipGetPortalLinkUrl(ctx context.Context, req *pb.RpcMembershipGetPortalLinkUrlRequest) *pb.RpcMembershipGetPortalLinkUrlResponse {
|
||||
ps := getService[payments.Service](mw)
|
||||
out, err := ps.GetPortalLink(ctx, req)
|
||||
|
||||
if err != nil {
|
||||
return &pb.RpcMembershipGetPortalLinkUrlResponse{
|
||||
Error: &pb.RpcMembershipGetPortalLinkUrlResponseError{
|
||||
Code: pb.RpcMembershipGetPortalLinkUrlResponseError_UNKNOWN_ERROR,
|
||||
Description: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (mw *Middleware) MembershipGetVerificationEmail(ctx context.Context, req *pb.RpcMembershipGetVerificationEmailRequest) *pb.RpcMembershipGetVerificationEmailResponse {
|
||||
ps := getService[payments.Service](mw)
|
||||
out, err := ps.GetVerificationEmail(ctx, req)
|
||||
|
||||
if err != nil {
|
||||
return &pb.RpcMembershipGetVerificationEmailResponse{
|
||||
Error: &pb.RpcMembershipGetVerificationEmailResponseError{
|
||||
Code: pb.RpcMembershipGetVerificationEmailResponseError_UNKNOWN_ERROR,
|
||||
Description: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (mw *Middleware) MembershipVerifyEmailCode(ctx context.Context, req *pb.RpcMembershipVerifyEmailCodeRequest) *pb.RpcMembershipVerifyEmailCodeResponse {
|
||||
ps := getService[payments.Service](mw)
|
||||
out, err := ps.VerifyEmailCode(ctx, req)
|
||||
|
||||
if err != nil {
|
||||
return &pb.RpcMembershipVerifyEmailCodeResponse{
|
||||
Error: &pb.RpcMembershipVerifyEmailCodeResponseError{
|
||||
Code: pb.RpcMembershipVerifyEmailCodeResponseError_UNKNOWN_ERROR,
|
||||
Description: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (mw *Middleware) MembershipFinalize(ctx context.Context, req *pb.RpcMembershipFinalizeRequest) *pb.RpcMembershipFinalizeResponse {
|
||||
ps := getService[payments.Service](mw)
|
||||
out, err := ps.FinalizeSubscription(ctx, req)
|
||||
|
||||
if err != nil {
|
||||
return &pb.RpcMembershipFinalizeResponse{
|
||||
Error: &pb.RpcMembershipFinalizeResponseError{
|
||||
Code: pb.RpcMembershipFinalizeResponseError_UNKNOWN_ERROR,
|
||||
Description: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (mw *Middleware) MembershipGetTiers(ctx context.Context, req *pb.RpcMembershipTiersGetRequest) *pb.RpcMembershipTiersGetResponse {
|
||||
ps := getService[payments.Service](mw)
|
||||
out, err := ps.GetTiers(ctx, req)
|
||||
|
||||
if err != nil {
|
||||
return &pb.RpcMembershipTiersGetResponse{
|
||||
Error: &pb.RpcMembershipTiersGetResponseError{
|
||||
Code: pb.RpcMembershipTiersGetResponseError_UNKNOWN_ERROR,
|
||||
Description: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
294
core/payments/cache/cache.go
vendored
Normal file
294
core/payments/cache/cache.go
vendored
Normal file
|
@ -0,0 +1,294 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/datastore"
|
||||
)
|
||||
|
||||
const CName = "cache"
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
var (
|
||||
ErrCacheDbError = errors.New("cache db error")
|
||||
ErrUnsupportedCacheVersion = errors.New("unsupported cache version")
|
||||
ErrCacheDisabled = errors.New("cache is disabled")
|
||||
ErrCacheExpired = errors.New("cache is empty")
|
||||
)
|
||||
|
||||
const dbKey = "payments/subscription/v1"
|
||||
|
||||
type StorageStructV1 struct {
|
||||
// to migrate old storage to new format
|
||||
CurrentVersion uint16
|
||||
|
||||
// this variable is just for info
|
||||
LastUpdated time.Time
|
||||
|
||||
// depending on the type of the subscription the cache will have different lifetime
|
||||
// if current time is >= ExpireTime -> cache is expired
|
||||
ExpireTime time.Time
|
||||
|
||||
// if this is 0 - then cache is enabled
|
||||
DisableUntilTime time.Time
|
||||
|
||||
// v1 of the actual data
|
||||
SubscriptionStatus pb.RpcMembershipGetStatusResponse
|
||||
|
||||
TiersData pb.RpcMembershipTiersGetResponse
|
||||
}
|
||||
|
||||
func newStorageStructV1() *StorageStructV1 {
|
||||
return &StorageStructV1{
|
||||
CurrentVersion: 1,
|
||||
LastUpdated: time.Now().UTC(),
|
||||
ExpireTime: time.Time{},
|
||||
DisableUntilTime: time.Time{},
|
||||
SubscriptionStatus: pb.RpcMembershipGetStatusResponse{
|
||||
Data: nil,
|
||||
},
|
||||
TiersData: pb.RpcMembershipTiersGetResponse{
|
||||
Tiers: nil,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type CacheService interface {
|
||||
// if cache is disabled -> will return objects and ErrCacheDisabled
|
||||
// if cache is expired -> will return objects and ErrCacheExpired
|
||||
CacheGet() (status *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipTiersGetResponse, err error)
|
||||
|
||||
// if cache is disabled -> will return no error
|
||||
// if cache is expired -> will return no error
|
||||
// status or tiers can be nil depending on what you want to update
|
||||
CacheSet(status *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipTiersGetResponse, ExpireTime time.Time) (err error)
|
||||
|
||||
IsCacheEnabled() (enabled bool)
|
||||
|
||||
// if already enabled -> will not return error
|
||||
CacheEnable() (err error)
|
||||
|
||||
// if already disabled -> will not return error
|
||||
// if currently disabled -> will disable GETs for next N minutes
|
||||
CacheDisableForNextMinutes(minutes int) (err error)
|
||||
|
||||
// does not take into account if cache is enabled or not, erases always
|
||||
CacheClear() (err error)
|
||||
|
||||
app.Component
|
||||
}
|
||||
|
||||
func New() CacheService {
|
||||
return &cacheservice{}
|
||||
}
|
||||
|
||||
type cacheservice struct {
|
||||
dbProvider datastore.Datastore
|
||||
db *badger.DB
|
||||
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
func (s *cacheservice) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (s *cacheservice) Init(a *app.App) (err error) {
|
||||
s.dbProvider = app.MustComponent[datastore.Datastore](a)
|
||||
|
||||
db, err := s.dbProvider.LocalStorage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.db = db
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *cacheservice) Run(_ context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *cacheservice) Close(_ context.Context) (err error) {
|
||||
return s.db.Close()
|
||||
}
|
||||
|
||||
func (s *cacheservice) CacheGet() (status *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipTiersGetResponse, err error) {
|
||||
// 1 - check in storage
|
||||
ss, err := s.get()
|
||||
if err != nil {
|
||||
log.Error("can not get subscription status from cache", zap.Error(err))
|
||||
return nil, nil, ErrCacheDbError
|
||||
}
|
||||
|
||||
if ss.CurrentVersion != 1 {
|
||||
// currently we have only one version, but in future we can have more
|
||||
// this error can happen if you "downgrade" the app
|
||||
log.Error("unsupported cache version", zap.Uint16("version", ss.CurrentVersion))
|
||||
return nil, nil, ErrUnsupportedCacheVersion
|
||||
}
|
||||
|
||||
// 2 - check if cache is disabled
|
||||
if !s.IsCacheEnabled() {
|
||||
// return object too
|
||||
return &ss.SubscriptionStatus, &ss.TiersData, ErrCacheDisabled
|
||||
}
|
||||
|
||||
// 3 - check if cache is outdated
|
||||
if time.Now().UTC().After(ss.ExpireTime) {
|
||||
// return object too
|
||||
return &ss.SubscriptionStatus, &ss.TiersData, ErrCacheExpired
|
||||
}
|
||||
|
||||
// 4 - return value
|
||||
return &ss.SubscriptionStatus, &ss.TiersData, nil
|
||||
}
|
||||
|
||||
func (s *cacheservice) CacheSet(status *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipTiersGetResponse, expireTime time.Time) (err error) {
|
||||
// 1 - get existing storage
|
||||
ss, err := s.get()
|
||||
if err != nil {
|
||||
// if there is no record in the cache, let's create it
|
||||
ss = newStorageStructV1()
|
||||
}
|
||||
|
||||
// 2 - update storage
|
||||
if status != nil {
|
||||
ss.SubscriptionStatus = *status
|
||||
}
|
||||
|
||||
if tiers != nil {
|
||||
ss.TiersData = *tiers
|
||||
}
|
||||
|
||||
ss.ExpireTime = expireTime
|
||||
|
||||
// 3 - save to storage
|
||||
return s.set(ss)
|
||||
}
|
||||
|
||||
func (s *cacheservice) IsCacheEnabled() (enabled bool) {
|
||||
// 1 - get existing storage
|
||||
ss, err := s.get()
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// 2 - check if cache is disabled
|
||||
if (ss.DisableUntilTime != time.Time{}) && time.Now().UTC().Before(ss.DisableUntilTime) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// will not return error if already enabled
|
||||
func (s *cacheservice) CacheEnable() (err error) {
|
||||
// 1 - get existing storage
|
||||
ss, err := s.get()
|
||||
if err != nil {
|
||||
// if there is no record in the cache, let's create it
|
||||
ss = newStorageStructV1()
|
||||
}
|
||||
|
||||
// 2 - update storage
|
||||
ss.DisableUntilTime = time.Time{}
|
||||
|
||||
// 3 - save to storage
|
||||
err = s.set(ss)
|
||||
if err != nil {
|
||||
return ErrCacheDbError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// will not return error if already disabled
|
||||
// if currently disabled - will disable for next N minutes
|
||||
func (s *cacheservice) CacheDisableForNextMinutes(minutes int) (err error) {
|
||||
// 1 - get existing storage
|
||||
ss, err := s.get()
|
||||
if err != nil {
|
||||
// if there is no record in the cache, let's create it
|
||||
ss = newStorageStructV1()
|
||||
}
|
||||
|
||||
// 2 - update storage
|
||||
ss.DisableUntilTime = time.Now().UTC().Add(time.Minute * time.Duration(minutes))
|
||||
|
||||
// 3 - save to storage
|
||||
err = s.set(ss)
|
||||
if err != nil {
|
||||
return ErrCacheDbError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// does not take into account if cache is enabled or not, erases always
|
||||
func (s *cacheservice) CacheClear() (err error) {
|
||||
// 1 - get existing storage
|
||||
_, err = s.get()
|
||||
if err != nil {
|
||||
// no error if there is no record in the cache
|
||||
return nil
|
||||
}
|
||||
|
||||
// 2 - update storage
|
||||
ss := newStorageStructV1()
|
||||
|
||||
// 3 - save to storage
|
||||
err = s.set(ss)
|
||||
if err != nil {
|
||||
return ErrCacheDbError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *cacheservice) get() (out *StorageStructV1, err error) {
|
||||
if s.db == nil {
|
||||
return nil, errors.New("db is not initialized")
|
||||
}
|
||||
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
|
||||
var ss StorageStructV1
|
||||
err = s.db.View(func(txn *badger.Txn) error {
|
||||
item, err := txn.Get([]byte(dbKey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return item.Value(func(val []byte) error {
|
||||
// convert value to out
|
||||
return json.Unmarshal(val, &ss)
|
||||
})
|
||||
})
|
||||
|
||||
out = &ss
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (s *cacheservice) set(in *StorageStructV1) (err error) {
|
||||
s.m.Lock()
|
||||
defer s.m.Unlock()
|
||||
|
||||
return s.db.Update(func(txn *badger.Txn) error {
|
||||
// convert
|
||||
bytes, err := json.Marshal(*in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return txn.Set([]byte(dbKey), bytes)
|
||||
})
|
||||
}
|
384
core/payments/cache/cache_test.go
vendored
Normal file
384
core/payments/cache/cache_test.go
vendored
Normal file
|
@ -0,0 +1,384 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
type fixture struct {
|
||||
a *app.App
|
||||
|
||||
*cacheservice
|
||||
}
|
||||
|
||||
func newFixture(t *testing.T) *fixture {
|
||||
fx := &fixture{
|
||||
a: new(app.App),
|
||||
cacheservice: New().(*cacheservice),
|
||||
}
|
||||
|
||||
db, err := badger.Open(badger.DefaultOptions("").WithInMemory(true))
|
||||
require.NoError(t, err)
|
||||
fx.db = db
|
||||
|
||||
//fx.a.Register(fx.ts)
|
||||
|
||||
require.NoError(t, fx.a.Start(ctx))
|
||||
return fx
|
||||
}
|
||||
|
||||
func (fx *fixture) finish(t *testing.T) {
|
||||
assert.NoError(t, fx.a.Close(ctx))
|
||||
|
||||
//assert.NoError(t, fx.db.Close())
|
||||
}
|
||||
|
||||
func TestPayments_EnableCache(t *testing.T) {
|
||||
t.Run("should succeed", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
err := fx.CacheEnable()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("should succeed even when called twice", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
err := fx.CacheEnable()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = fx.CacheEnable()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPayments_DisableCache(t *testing.T) {
|
||||
t.Run("should succeed with 0", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
err := fx.CacheDisableForNextMinutes(0)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("should succeed even when called twice", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
err := fx.CacheDisableForNextMinutes(60)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = fx.CacheDisableForNextMinutes(40)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("clear cache should remove disabling", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
err := fx.CacheDisableForNextMinutes(60)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = fx.CacheGet()
|
||||
require.Equal(t, ErrCacheDisabled, err)
|
||||
|
||||
err = fx.CacheClear()
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = fx.CacheGet()
|
||||
require.Equal(t, ErrCacheExpired, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPayments_ClearCache(t *testing.T) {
|
||||
t.Run("should succeed even if no cache in the DB", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
err := fx.CacheClear()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("should succeed even when called twice", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
err := fx.CacheClear()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = fx.CacheClear()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("should succeed when cache is disabled", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
err := fx.CacheDisableForNextMinutes(60)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = fx.CacheClear()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("should succeed when cache is in DB", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
timeNow := time.Now().UTC()
|
||||
timePlus5Hours := timeNow.Add(5 * time.Hour)
|
||||
|
||||
err := fx.CacheSet(&pb.RpcMembershipGetStatusResponse{
|
||||
Data: &model.Membership{
|
||||
Tier: int32(model.Membership_TierExplorer),
|
||||
Status: model.Membership_StatusActive,
|
||||
},
|
||||
},
|
||||
&pb.RpcMembershipTiersGetResponse{
|
||||
Tiers: []*model.MembershipTierData{},
|
||||
},
|
||||
timePlus5Hours,
|
||||
)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
err = fx.CacheClear()
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = fx.CacheGet()
|
||||
require.Equal(t, ErrCacheExpired, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPayments_CacheGetSubscriptionStatus(t *testing.T) {
|
||||
t.Run("should fail if no record in the DB", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
_, _, err := fx.CacheGet()
|
||||
require.Equal(t, ErrCacheDbError, err)
|
||||
})
|
||||
|
||||
t.Run("should succeed", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
timeNow := time.Now().UTC()
|
||||
timePlus5Hours := timeNow.Add(5 * time.Hour)
|
||||
|
||||
err := fx.CacheSet(&pb.RpcMembershipGetStatusResponse{
|
||||
Data: &model.Membership{
|
||||
Tier: int32(model.Membership_TierExplorer),
|
||||
Status: model.Membership_StatusActive,
|
||||
},
|
||||
},
|
||||
&pb.RpcMembershipTiersGetResponse{
|
||||
Tiers: []*model.MembershipTierData{},
|
||||
},
|
||||
timePlus5Hours)
|
||||
require.NoError(t, err)
|
||||
|
||||
out, _, err := fx.CacheGet()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int32(model.Membership_TierExplorer), out.Data.Tier)
|
||||
require.Equal(t, model.Membership_StatusActive, out.Data.Status)
|
||||
|
||||
err = fx.CacheSet(&pb.RpcMembershipGetStatusResponse{
|
||||
Data: &model.Membership{
|
||||
Tier: int32(model.Membership_TierExplorer),
|
||||
// here
|
||||
Status: model.Membership_StatusUnknown,
|
||||
},
|
||||
},
|
||||
&pb.RpcMembershipTiersGetResponse{
|
||||
Tiers: []*model.MembershipTierData{},
|
||||
},
|
||||
timePlus5Hours)
|
||||
require.NoError(t, err)
|
||||
|
||||
out, _, err = fx.CacheGet()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int32(model.Membership_TierExplorer), out.Data.Tier)
|
||||
require.Equal(t, model.Membership_StatusUnknown, out.Data.Status)
|
||||
})
|
||||
|
||||
t.Run("should return object and error if cache is disabled", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
en := fx.IsCacheEnabled()
|
||||
require.Equal(t, true, en)
|
||||
|
||||
err := fx.CacheDisableForNextMinutes(10)
|
||||
require.NoError(t, err)
|
||||
|
||||
en = fx.IsCacheEnabled()
|
||||
require.Equal(t, false, en)
|
||||
|
||||
timeNow := time.Now().UTC()
|
||||
timePlus5Hours := timeNow.Add(5 * time.Hour)
|
||||
|
||||
err = fx.CacheSet(&pb.RpcMembershipGetStatusResponse{
|
||||
Data: &model.Membership{
|
||||
Tier: int32(model.Membership_TierExplorer),
|
||||
Status: model.Membership_StatusActive,
|
||||
},
|
||||
},
|
||||
&pb.RpcMembershipTiersGetResponse{
|
||||
Tiers: []*model.MembershipTierData{},
|
||||
},
|
||||
timePlus5Hours)
|
||||
require.NoError(t, err)
|
||||
|
||||
out, _, err := fx.CacheGet()
|
||||
require.Equal(t, ErrCacheDisabled, err)
|
||||
// HERE: weird semantics, error is returned too :-)
|
||||
require.Equal(t, int32(model.Membership_TierExplorer), out.Data.Tier)
|
||||
|
||||
err = fx.CacheEnable()
|
||||
require.NoError(t, err)
|
||||
|
||||
en = fx.IsCacheEnabled()
|
||||
require.Equal(t, true, en)
|
||||
|
||||
out, _, err = fx.CacheGet()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int32(model.Membership_TierExplorer), out.Data.Tier)
|
||||
})
|
||||
|
||||
t.Run("should return error if cache is cleared", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
timeNow := time.Now().UTC()
|
||||
timePlus5Hours := timeNow.Add(5 * time.Hour)
|
||||
|
||||
err := fx.CacheSet(&pb.RpcMembershipGetStatusResponse{
|
||||
Data: &model.Membership{
|
||||
Tier: int32(model.Membership_TierExplorer),
|
||||
Status: model.Membership_StatusActive,
|
||||
},
|
||||
},
|
||||
&pb.RpcMembershipTiersGetResponse{
|
||||
Tiers: []*model.MembershipTierData{},
|
||||
},
|
||||
timePlus5Hours)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = fx.CacheClear()
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = fx.CacheGet()
|
||||
require.Equal(t, ErrCacheExpired, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPayments_CacheSetSubscriptionStatus(t *testing.T) {
|
||||
t.Run("should succeed if no record was in the DB", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
timeNow := time.Now().UTC()
|
||||
timePlus5Hours := timeNow.Add(5 * time.Hour)
|
||||
|
||||
err := fx.CacheSet(&pb.RpcMembershipGetStatusResponse{
|
||||
Data: &model.Membership{
|
||||
Tier: int32(model.Membership_TierExplorer),
|
||||
Status: model.Membership_StatusActive,
|
||||
},
|
||||
},
|
||||
&pb.RpcMembershipTiersGetResponse{
|
||||
Tiers: []*model.MembershipTierData{},
|
||||
},
|
||||
timePlus5Hours)
|
||||
require.Equal(t, nil, err)
|
||||
|
||||
out, _, err := fx.CacheGet()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int32(model.Membership_TierExplorer), out.Data.Tier)
|
||||
require.Equal(t, model.Membership_StatusActive, out.Data.Status)
|
||||
})
|
||||
|
||||
t.Run("should succeed if cache is disabled", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
err := fx.CacheDisableForNextMinutes(10)
|
||||
require.NoError(t, err)
|
||||
|
||||
timeNow := time.Now().UTC()
|
||||
timePlus5Hours := timeNow.Add(5 * time.Hour)
|
||||
|
||||
err = fx.CacheSet(&pb.RpcMembershipGetStatusResponse{
|
||||
Data: &model.Membership{
|
||||
Tier: int32(model.Membership_TierExplorer),
|
||||
Status: model.Membership_StatusActive,
|
||||
},
|
||||
},
|
||||
&pb.RpcMembershipTiersGetResponse{
|
||||
Tiers: []*model.MembershipTierData{},
|
||||
},
|
||||
timePlus5Hours)
|
||||
require.Equal(t, nil, err)
|
||||
})
|
||||
|
||||
t.Run("should succeed if cache is cleared", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
err := fx.CacheClear()
|
||||
require.NoError(t, err)
|
||||
|
||||
timeNow := time.Now().UTC()
|
||||
timePlus5Hours := timeNow.Add(5 * time.Hour)
|
||||
|
||||
err = fx.CacheSet(&pb.RpcMembershipGetStatusResponse{
|
||||
Data: &model.Membership{
|
||||
Tier: int32(model.Membership_TierExplorer),
|
||||
Status: model.Membership_StatusActive,
|
||||
},
|
||||
},
|
||||
&pb.RpcMembershipTiersGetResponse{
|
||||
Tiers: []*model.MembershipTierData{},
|
||||
},
|
||||
timePlus5Hours)
|
||||
require.Equal(t, nil, err)
|
||||
})
|
||||
|
||||
t.Run("should succeed if expire is set to 0", func(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
defer fx.finish(t)
|
||||
|
||||
err := fx.CacheClear()
|
||||
require.NoError(t, err)
|
||||
|
||||
timeNull := time.Time{}
|
||||
|
||||
err = fx.CacheSet(&pb.RpcMembershipGetStatusResponse{
|
||||
Data: &model.Membership{
|
||||
Tier: int32(model.Membership_TierExplorer),
|
||||
Status: model.Membership_StatusActive,
|
||||
},
|
||||
},
|
||||
&pb.RpcMembershipTiersGetResponse{
|
||||
Tiers: []*model.MembershipTierData{},
|
||||
},
|
||||
timeNull)
|
||||
require.Equal(t, nil, err)
|
||||
|
||||
_, _, err = fx.CacheGet()
|
||||
require.Equal(t, ErrCacheExpired, err)
|
||||
})
|
||||
}
|
426
core/payments/cache/mock_cache/mock_CacheService.go
vendored
Normal file
426
core/payments/cache/mock_cache/mock_CacheService.go
vendored
Normal file
|
@ -0,0 +1,426 @@
|
|||
// Code generated by mockery. DO NOT EDIT.
|
||||
|
||||
package mock_cache
|
||||
|
||||
import (
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
pb "github.com/anyproto/anytype-heart/pb"
|
||||
|
||||
time "time"
|
||||
)
|
||||
|
||||
// MockCacheService is an autogenerated mock type for the CacheService type
|
||||
type MockCacheService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockCacheService_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockCacheService) EXPECT() *MockCacheService_Expecter {
|
||||
return &MockCacheService_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// CacheClear provides a mock function with given fields:
|
||||
func (_m *MockCacheService) CacheClear() error {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CacheClear")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCacheService_CacheClear_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CacheClear'
|
||||
type MockCacheService_CacheClear_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CacheClear is a helper method to define mock.On call
|
||||
func (_e *MockCacheService_Expecter) CacheClear() *MockCacheService_CacheClear_Call {
|
||||
return &MockCacheService_CacheClear_Call{Call: _e.mock.On("CacheClear")}
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_CacheClear_Call) Run(run func()) *MockCacheService_CacheClear_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_CacheClear_Call) Return(err error) *MockCacheService_CacheClear_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_CacheClear_Call) RunAndReturn(run func() error) *MockCacheService_CacheClear_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CacheDisableForNextMinutes provides a mock function with given fields: minutes
|
||||
func (_m *MockCacheService) CacheDisableForNextMinutes(minutes int) error {
|
||||
ret := _m.Called(minutes)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CacheDisableForNextMinutes")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int) error); ok {
|
||||
r0 = rf(minutes)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCacheService_CacheDisableForNextMinutes_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CacheDisableForNextMinutes'
|
||||
type MockCacheService_CacheDisableForNextMinutes_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CacheDisableForNextMinutes is a helper method to define mock.On call
|
||||
// - minutes int
|
||||
func (_e *MockCacheService_Expecter) CacheDisableForNextMinutes(minutes interface{}) *MockCacheService_CacheDisableForNextMinutes_Call {
|
||||
return &MockCacheService_CacheDisableForNextMinutes_Call{Call: _e.mock.On("CacheDisableForNextMinutes", minutes)}
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_CacheDisableForNextMinutes_Call) Run(run func(minutes int)) *MockCacheService_CacheDisableForNextMinutes_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(int))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_CacheDisableForNextMinutes_Call) Return(err error) *MockCacheService_CacheDisableForNextMinutes_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_CacheDisableForNextMinutes_Call) RunAndReturn(run func(int) error) *MockCacheService_CacheDisableForNextMinutes_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CacheEnable provides a mock function with given fields:
|
||||
func (_m *MockCacheService) CacheEnable() error {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CacheEnable")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCacheService_CacheEnable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CacheEnable'
|
||||
type MockCacheService_CacheEnable_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CacheEnable is a helper method to define mock.On call
|
||||
func (_e *MockCacheService_Expecter) CacheEnable() *MockCacheService_CacheEnable_Call {
|
||||
return &MockCacheService_CacheEnable_Call{Call: _e.mock.On("CacheEnable")}
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_CacheEnable_Call) Run(run func()) *MockCacheService_CacheEnable_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_CacheEnable_Call) Return(err error) *MockCacheService_CacheEnable_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_CacheEnable_Call) RunAndReturn(run func() error) *MockCacheService_CacheEnable_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CacheGet provides a mock function with given fields:
|
||||
func (_m *MockCacheService) CacheGet() (*pb.RpcMembershipGetStatusResponse, *pb.RpcMembershipTiersGetResponse, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CacheGet")
|
||||
}
|
||||
|
||||
var r0 *pb.RpcMembershipGetStatusResponse
|
||||
var r1 *pb.RpcMembershipTiersGetResponse
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(0).(func() (*pb.RpcMembershipGetStatusResponse, *pb.RpcMembershipTiersGetResponse, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() *pb.RpcMembershipGetStatusResponse); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*pb.RpcMembershipGetStatusResponse)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() *pb.RpcMembershipTiersGetResponse); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*pb.RpcMembershipTiersGetResponse)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(2).(func() error); ok {
|
||||
r2 = rf()
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// MockCacheService_CacheGet_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CacheGet'
|
||||
type MockCacheService_CacheGet_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CacheGet is a helper method to define mock.On call
|
||||
func (_e *MockCacheService_Expecter) CacheGet() *MockCacheService_CacheGet_Call {
|
||||
return &MockCacheService_CacheGet_Call{Call: _e.mock.On("CacheGet")}
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_CacheGet_Call) Run(run func()) *MockCacheService_CacheGet_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_CacheGet_Call) Return(status *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipTiersGetResponse, err error) *MockCacheService_CacheGet_Call {
|
||||
_c.Call.Return(status, tiers, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_CacheGet_Call) RunAndReturn(run func() (*pb.RpcMembershipGetStatusResponse, *pb.RpcMembershipTiersGetResponse, error)) *MockCacheService_CacheGet_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CacheSet provides a mock function with given fields: status, tiers, ExpireTime
|
||||
func (_m *MockCacheService) CacheSet(status *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipTiersGetResponse, ExpireTime time.Time) error {
|
||||
ret := _m.Called(status, tiers, ExpireTime)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CacheSet")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*pb.RpcMembershipGetStatusResponse, *pb.RpcMembershipTiersGetResponse, time.Time) error); ok {
|
||||
r0 = rf(status, tiers, ExpireTime)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCacheService_CacheSet_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CacheSet'
|
||||
type MockCacheService_CacheSet_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CacheSet is a helper method to define mock.On call
|
||||
// - status *pb.RpcMembershipGetStatusResponse
|
||||
// - tiers *pb.RpcMembershipTiersGetResponse
|
||||
// - ExpireTime time.Time
|
||||
func (_e *MockCacheService_Expecter) CacheSet(status interface{}, tiers interface{}, ExpireTime interface{}) *MockCacheService_CacheSet_Call {
|
||||
return &MockCacheService_CacheSet_Call{Call: _e.mock.On("CacheSet", status, tiers, ExpireTime)}
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_CacheSet_Call) Run(run func(status *pb.RpcMembershipGetStatusResponse, tiers *pb.RpcMembershipTiersGetResponse, ExpireTime time.Time)) *MockCacheService_CacheSet_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(*pb.RpcMembershipGetStatusResponse), args[1].(*pb.RpcMembershipTiersGetResponse), args[2].(time.Time))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_CacheSet_Call) Return(err error) *MockCacheService_CacheSet_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_CacheSet_Call) RunAndReturn(run func(*pb.RpcMembershipGetStatusResponse, *pb.RpcMembershipTiersGetResponse, time.Time) error) *MockCacheService_CacheSet_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Init provides a mock function with given fields: a
|
||||
func (_m *MockCacheService) Init(a *app.App) error {
|
||||
ret := _m.Called(a)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Init")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*app.App) error); ok {
|
||||
r0 = rf(a)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCacheService_Init_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Init'
|
||||
type MockCacheService_Init_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Init is a helper method to define mock.On call
|
||||
// - a *app.App
|
||||
func (_e *MockCacheService_Expecter) Init(a interface{}) *MockCacheService_Init_Call {
|
||||
return &MockCacheService_Init_Call{Call: _e.mock.On("Init", a)}
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_Init_Call) Run(run func(a *app.App)) *MockCacheService_Init_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(*app.App))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_Init_Call) Return(err error) *MockCacheService_Init_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_Init_Call) RunAndReturn(run func(*app.App) error) *MockCacheService_Init_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// IsCacheEnabled provides a mock function with given fields:
|
||||
func (_m *MockCacheService) IsCacheEnabled() bool {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for IsCacheEnabled")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func() bool); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCacheService_IsCacheEnabled_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsCacheEnabled'
|
||||
type MockCacheService_IsCacheEnabled_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// IsCacheEnabled is a helper method to define mock.On call
|
||||
func (_e *MockCacheService_Expecter) IsCacheEnabled() *MockCacheService_IsCacheEnabled_Call {
|
||||
return &MockCacheService_IsCacheEnabled_Call{Call: _e.mock.On("IsCacheEnabled")}
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_IsCacheEnabled_Call) Run(run func()) *MockCacheService_IsCacheEnabled_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_IsCacheEnabled_Call) Return(enabled bool) *MockCacheService_IsCacheEnabled_Call {
|
||||
_c.Call.Return(enabled)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_IsCacheEnabled_Call) RunAndReturn(run func() bool) *MockCacheService_IsCacheEnabled_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Name provides a mock function with given fields:
|
||||
func (_m *MockCacheService) Name() string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Name")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockCacheService_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name'
|
||||
type MockCacheService_Name_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Name is a helper method to define mock.On call
|
||||
func (_e *MockCacheService_Expecter) Name() *MockCacheService_Name_Call {
|
||||
return &MockCacheService_Name_Call{Call: _e.mock.On("Name")}
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_Name_Call) Run(run func()) *MockCacheService_Name_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_Name_Call) Return(name string) *MockCacheService_Name_Call {
|
||||
_c.Call.Return(name)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockCacheService_Name_Call) RunAndReturn(run func() string) *MockCacheService_Name_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockCacheService creates a new instance of MockCacheService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockCacheService(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockCacheService {
|
||||
mock := &MockCacheService{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
696
core/payments/payments.go
Normal file
696
core/payments/payments.go
Normal file
|
@ -0,0 +1,696 @@
|
|||
package payments
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/util/periodicsync"
|
||||
"go.uber.org/zap"
|
||||
|
||||
ppclient "github.com/anyproto/any-sync/paymentservice/paymentserviceclient"
|
||||
psp "github.com/anyproto/any-sync/paymentservice/paymentserviceproto"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/event"
|
||||
"github.com/anyproto/anytype-heart/core/payments/cache"
|
||||
"github.com/anyproto/anytype-heart/core/wallet"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/logging"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
)
|
||||
|
||||
const CName = "payments"
|
||||
|
||||
var log = logging.Logger(CName).Desugar()
|
||||
|
||||
const (
|
||||
refreshIntervalSecs = 10
|
||||
timeout = 10 * time.Second
|
||||
initialStatus = -1
|
||||
)
|
||||
|
||||
type globalNamesUpdater interface {
|
||||
UpdateGlobalNames()
|
||||
}
|
||||
|
||||
/*
|
||||
CACHE LOGICS:
|
||||
|
||||
1. User installs Anytype
|
||||
-> cache is clean
|
||||
|
||||
2. client gets his subscription from MW
|
||||
|
||||
- if cache is disabled and 30 minutes elapsed
|
||||
-> enable cache again
|
||||
|
||||
- if cache is disabled or cache is clean or cache is expired
|
||||
-> ask from PP node, then save to cache:
|
||||
|
||||
x if got no info -> cache it for N days
|
||||
x if got into without expiration -> cache it for N days
|
||||
x if got info -> cache it for until it expires
|
||||
x if cache was disabled before and tier has changed or status is active -> enable cache again
|
||||
x if can not connect to PP node -> return error
|
||||
x if can not write to cache -> return error
|
||||
|
||||
- if we have it in cache
|
||||
-> return from cache
|
||||
|
||||
3. User clicks on a “Pay by card/crypto” or “Manage” button:
|
||||
-> disable cache for 30 minutes (so we always get from PP node)
|
||||
|
||||
4. User confirms his e-mail code
|
||||
-> clear cache (it will cause getting again from PP node next)
|
||||
*/
|
||||
type Service interface {
|
||||
GetSubscriptionStatus(ctx context.Context, req *pb.RpcMembershipGetStatusRequest) (*pb.RpcMembershipGetStatusResponse, error)
|
||||
IsNameValid(ctx context.Context, req *pb.RpcMembershipIsNameValidRequest) (*pb.RpcMembershipIsNameValidResponse, error)
|
||||
GetPaymentURL(ctx context.Context, req *pb.RpcMembershipGetPaymentUrlRequest) (*pb.RpcMembershipGetPaymentUrlResponse, error)
|
||||
GetPortalLink(ctx context.Context, req *pb.RpcMembershipGetPortalLinkUrlRequest) (*pb.RpcMembershipGetPortalLinkUrlResponse, error)
|
||||
GetVerificationEmail(ctx context.Context, req *pb.RpcMembershipGetVerificationEmailRequest) (*pb.RpcMembershipGetVerificationEmailResponse, error)
|
||||
VerifyEmailCode(ctx context.Context, req *pb.RpcMembershipVerifyEmailCodeRequest) (*pb.RpcMembershipVerifyEmailCodeResponse, error)
|
||||
FinalizeSubscription(ctx context.Context, req *pb.RpcMembershipFinalizeRequest) (*pb.RpcMembershipFinalizeResponse, error)
|
||||
GetTiers(ctx context.Context, req *pb.RpcMembershipTiersGetRequest) (*pb.RpcMembershipTiersGetResponse, error)
|
||||
|
||||
app.ComponentRunnable
|
||||
}
|
||||
|
||||
func New() Service {
|
||||
return &service{}
|
||||
}
|
||||
|
||||
type service struct {
|
||||
cache cache.CacheService
|
||||
ppclient ppclient.AnyPpClientService
|
||||
wallet wallet.Wallet
|
||||
mx sync.Mutex
|
||||
periodicGetStatus periodicsync.PeriodicSync
|
||||
eventSender event.Sender
|
||||
profileUpdater globalNamesUpdater
|
||||
}
|
||||
|
||||
func (s *service) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (s *service) Init(a *app.App) (err error) {
|
||||
s.cache = app.MustComponent[cache.CacheService](a)
|
||||
s.ppclient = app.MustComponent[ppclient.AnyPpClientService](a)
|
||||
s.wallet = app.MustComponent[wallet.Wallet](a)
|
||||
s.eventSender = app.MustComponent[event.Sender](a)
|
||||
s.periodicGetStatus = periodicsync.NewPeriodicSync(refreshIntervalSecs, timeout, s.getPeriodicStatus, logger.CtxLogger{Logger: log})
|
||||
s.profileUpdater = app.MustComponent[globalNamesUpdater](a)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Run(ctx context.Context) (err error) {
|
||||
// skip running loop if called from tests
|
||||
val := ctx.Value("dontRunPeriodicGetStatus")
|
||||
if val != nil && val.(bool) {
|
||||
return nil
|
||||
}
|
||||
|
||||
s.periodicGetStatus.Run()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Close(_ context.Context) (err error) {
|
||||
s.periodicGetStatus.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) getPeriodicStatus(ctx context.Context) error {
|
||||
// get subscription status (from cache or from PP node)
|
||||
// if status is changed -> it will send an event
|
||||
log.Debug("periodic: getting subscription status from cache/PP node")
|
||||
|
||||
_, err := s.GetSubscriptionStatus(ctx, &pb.RpcMembershipGetStatusRequest{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *service) sendEvent(status *pb.RpcMembershipGetStatusResponse) {
|
||||
s.eventSender.Broadcast(&pb.Event{
|
||||
Messages: []*pb.EventMessage{
|
||||
{
|
||||
Value: &pb.EventMessageValueOfMembershipUpdate{
|
||||
MembershipUpdate: &pb.EventMembershipUpdate{
|
||||
Data: status.Data,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *service) GetSubscriptionStatus(ctx context.Context, req *pb.RpcMembershipGetStatusRequest) (*pb.RpcMembershipGetStatusResponse, error) {
|
||||
s.mx.Lock()
|
||||
defer s.mx.Unlock()
|
||||
|
||||
ownerID := s.wallet.Account().SignKey.GetPublic().Account()
|
||||
privKey := s.wallet.GetAccountPrivkey()
|
||||
|
||||
// 1 - check in cache
|
||||
// tiers var. is unused here
|
||||
cachedStatus, _, err := s.cache.CacheGet()
|
||||
|
||||
// if NoCache -> skip returning from cache
|
||||
if !req.NoCache && (err == nil) && (cachedStatus != nil) && (cachedStatus.Data != nil) {
|
||||
log.Debug("returning subscription status from cache", zap.Error(err), zap.Any("cachedStatus", cachedStatus))
|
||||
return cachedStatus, nil
|
||||
}
|
||||
|
||||
// 2 - send request to PP node
|
||||
gsr := psp.GetSubscriptionRequest{
|
||||
// payment node will check if signature matches with this OwnerAnyID
|
||||
OwnerAnyID: ownerID,
|
||||
}
|
||||
|
||||
payload, err := gsr.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// this is the SignKey
|
||||
signature, err := privKey.Sign(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqSigned := psp.GetSubscriptionRequestSigned{
|
||||
Payload: payload,
|
||||
Signature: signature,
|
||||
}
|
||||
|
||||
log.Debug("get sub from PP node", zap.Any("cachedStatus", cachedStatus), zap.Bool("noCache", req.NoCache))
|
||||
|
||||
status, err := s.ppclient.GetSubscriptionStatus(ctx, &reqSigned)
|
||||
if err != nil {
|
||||
log.Info("creating empty subscription in cache because can not get subscription status from payment node")
|
||||
|
||||
// eat error and create empty status ("no tier") so that we will then save it to the cache
|
||||
status = &psp.GetSubscriptionResponse{
|
||||
Tier: int32(psp.SubscriptionTier_TierUnknown),
|
||||
Status: psp.SubscriptionStatus_StatusUnknown,
|
||||
}
|
||||
}
|
||||
|
||||
out := pb.RpcMembershipGetStatusResponse{
|
||||
Data: &model.Membership{},
|
||||
}
|
||||
|
||||
out.Data.Tier = status.Tier
|
||||
out.Data.Status = model.MembershipStatus(status.Status)
|
||||
out.Data.DateStarted = status.DateStarted
|
||||
out.Data.DateEnds = status.DateEnds
|
||||
out.Data.IsAutoRenew = status.IsAutoRenew
|
||||
out.Data.PaymentMethod = model.MembershipPaymentMethod(status.PaymentMethod)
|
||||
out.Data.RequestedAnyName = status.RequestedAnyName
|
||||
out.Data.UserEmail = status.UserEmail
|
||||
out.Data.SubscribeToNewsletter = status.SubscribeToNewsletter
|
||||
|
||||
// 3 - save into cache
|
||||
// truncate nseconds here
|
||||
var cacheExpireTime time.Time = time.Unix(int64(status.DateEnds), 0)
|
||||
isExpired := time.Now().UTC().After(cacheExpireTime)
|
||||
|
||||
// if subscription DateEns is null - then default expire time is in 10 days
|
||||
// or until user clicks on a “Pay by card/crypto” or “Manage” button
|
||||
if status.DateEnds == 0 || isExpired {
|
||||
log.Debug("setting cache to +1 day because subscription is isExpired")
|
||||
|
||||
timeNow := time.Now().UTC()
|
||||
cacheExpireTime = timeNow.Add(1 * 24 * time.Hour)
|
||||
}
|
||||
|
||||
// update only status, not tiers
|
||||
err = s.cache.CacheSet(&out, nil, cacheExpireTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isDiffTier := (cachedStatus != nil) && (cachedStatus.Data.Tier != status.Tier)
|
||||
isDiffStatus := (cachedStatus != nil) && (cachedStatus.Data.Status != model.MembershipStatus(status.Status))
|
||||
isDiffRequestedName := (cachedStatus != nil) && (cachedStatus.Data.RequestedAnyName != status.RequestedAnyName)
|
||||
|
||||
log.Debug("subscription status", zap.Any("from server", status), zap.Any("cached", cachedStatus))
|
||||
|
||||
// 4 - if cache was disabled but the tier is different or status is active
|
||||
if cachedStatus == nil || (isDiffTier || isDiffStatus) {
|
||||
log.Info("subscription status has changed. sending EventMembershipUpdate",
|
||||
zap.Bool("cache was empty", cachedStatus == nil),
|
||||
zap.Bool("isDiffTier", isDiffTier),
|
||||
zap.Bool("isDiffStatus", isDiffStatus),
|
||||
)
|
||||
|
||||
// 4.1 - send the event
|
||||
s.sendEvent(&out)
|
||||
|
||||
// 4.2 - enable cache again (we have received new data)
|
||||
log.Info("enabling cache again")
|
||||
|
||||
// or it will be automatically enabled after N minutes of DisableForNextMinutes() call
|
||||
err := s.cache.CacheEnable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 5 - if requested any name has changed, then we need to update details of local identity
|
||||
if isDiffRequestedName {
|
||||
s.profileUpdater.UpdateGlobalNames()
|
||||
}
|
||||
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (s *service) IsNameValid(ctx context.Context, req *pb.RpcMembershipIsNameValidRequest) (*pb.RpcMembershipIsNameValidResponse, error) {
|
||||
var code psp.IsNameValidResponse_Code
|
||||
var desc string
|
||||
|
||||
out := pb.RpcMembershipIsNameValidResponse{}
|
||||
|
||||
/*
|
||||
// 1 - send request to PP node and ask her please
|
||||
invr := psp.IsNameValidRequest{
|
||||
// payment node will check if signature matches with this OwnerAnyID
|
||||
RequestedTier: req.RequestedTier,
|
||||
RequestedAnyName: req.RequestedAnyName,
|
||||
}
|
||||
|
||||
resp, err := s.ppclient.IsNameValid(ctx, &invr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.Code == psp.IsNameValidResponse_Valid {
|
||||
// no error
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
out.Error = &pb.RpcMembershipIsNameValidResponseError{}
|
||||
code = resp.Code
|
||||
desc = resp.Description
|
||||
*/
|
||||
|
||||
// 1 - get all tiers from cache or PP node
|
||||
tiers, err := s.GetTiers(ctx, &pb.RpcMembershipTiersGetRequest{
|
||||
NoCache: false,
|
||||
// TODO: warning! no locale and payment method are passed here!
|
||||
// Locale: "",
|
||||
// PaymentMethod: pb.RpcMembershipPaymentMethod_PAYMENT_METHOD_UNKNOWN,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tiers.Tiers == nil {
|
||||
return nil, errors.New("no tiers received")
|
||||
}
|
||||
|
||||
// find req.RequestedTier
|
||||
var tier *model.MembershipTierData
|
||||
for _, t := range tiers.Tiers {
|
||||
if t.Id == uint32(req.RequestedTier) {
|
||||
tier = t
|
||||
break
|
||||
}
|
||||
}
|
||||
if tier == nil {
|
||||
return nil, errors.New("requested tier not found")
|
||||
}
|
||||
|
||||
code = s.validateAnyName(*tier, req.RequestedAnyName)
|
||||
|
||||
if code == psp.IsNameValidResponse_Valid {
|
||||
// valid
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// 2 - convert code to error
|
||||
out.Error = &pb.RpcMembershipIsNameValidResponseError{}
|
||||
|
||||
switch code {
|
||||
case psp.IsNameValidResponse_NoDotAny:
|
||||
out.Error.Code = pb.RpcMembershipIsNameValidResponseError_BAD_INPUT
|
||||
out.Error.Description = "No .any at the end of the name"
|
||||
case psp.IsNameValidResponse_TooShort:
|
||||
out.Error.Code = pb.RpcMembershipIsNameValidResponseError_TOO_SHORT
|
||||
case psp.IsNameValidResponse_TooLong:
|
||||
out.Error.Code = pb.RpcMembershipIsNameValidResponseError_TOO_LONG
|
||||
case psp.IsNameValidResponse_HasInvalidChars:
|
||||
out.Error.Code = pb.RpcMembershipIsNameValidResponseError_HAS_INVALID_CHARS
|
||||
case psp.IsNameValidResponse_TierFeatureNoName:
|
||||
out.Error.Code = pb.RpcMembershipIsNameValidResponseError_TIER_FEATURES_NO_NAME
|
||||
default:
|
||||
out.Error.Code = pb.RpcMembershipIsNameValidResponseError_UNKNOWN_ERROR
|
||||
}
|
||||
|
||||
out.Error.Description = desc
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (s *service) validateAnyName(tier model.MembershipTierData, name string) psp.IsNameValidResponse_Code {
|
||||
if name == "" {
|
||||
// empty name means we don't want to register name, and this is valid
|
||||
return psp.IsNameValidResponse_Valid
|
||||
}
|
||||
|
||||
// if name has no .any postfix -> error
|
||||
if len(name) < 4 || name[len(name)-4:] != ".any" {
|
||||
return psp.IsNameValidResponse_NoDotAny
|
||||
}
|
||||
|
||||
// for extra safety normalize name here too!
|
||||
name, err := normalizeAnyName(name)
|
||||
if err != nil {
|
||||
log.Debug("can not normalize name", zap.Error(err), zap.String("name", name))
|
||||
return psp.IsNameValidResponse_HasInvalidChars
|
||||
}
|
||||
|
||||
// remove .any postfix
|
||||
name = name[:len(name)-4]
|
||||
|
||||
// if minLen is zero - means "no check is required"
|
||||
if tier.AnyNamesCountIncluded == 0 {
|
||||
return psp.IsNameValidResponse_TierFeatureNoName
|
||||
}
|
||||
if tier.AnyNameMinLength == 0 {
|
||||
return psp.IsNameValidResponse_TierFeatureNoName
|
||||
}
|
||||
if uint32(utf8.RuneCountInString(name)) < tier.AnyNameMinLength {
|
||||
return psp.IsNameValidResponse_TooShort
|
||||
}
|
||||
|
||||
// valid
|
||||
return psp.IsNameValidResponse_Valid
|
||||
}
|
||||
|
||||
func (s *service) GetPaymentURL(ctx context.Context, req *pb.RpcMembershipGetPaymentUrlRequest) (*pb.RpcMembershipGetPaymentUrlResponse, error) {
|
||||
// 1 - send request
|
||||
bsr := psp.BuySubscriptionRequest{
|
||||
// payment node will check if signature matches with this OwnerAnyID
|
||||
OwnerAnyId: s.wallet.Account().SignKey.GetPublic().Account(),
|
||||
|
||||
// not SCW address, but EOA address
|
||||
// including 0x
|
||||
OwnerEthAddress: s.wallet.GetAccountEthAddress().Hex(),
|
||||
|
||||
RequestedTier: req.RequestedTier,
|
||||
PaymentMethod: psp.PaymentMethod(req.PaymentMethod),
|
||||
|
||||
RequestedAnyName: req.RequestedAnyName,
|
||||
}
|
||||
|
||||
payload, err := bsr.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
privKey := s.wallet.GetAccountPrivkey()
|
||||
signature, err := privKey.Sign(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqSigned := psp.BuySubscriptionRequestSigned{
|
||||
Payload: payload,
|
||||
Signature: signature,
|
||||
}
|
||||
|
||||
bsRet, err := s.ppclient.BuySubscription(ctx, &reqSigned)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var out pb.RpcMembershipGetPaymentUrlResponse
|
||||
out.PaymentUrl = bsRet.PaymentUrl
|
||||
|
||||
// 2 - disable cache for 30 minutes
|
||||
log.Debug("disabling cache for 30 minutes after payment URL was received")
|
||||
|
||||
err = s.cache.CacheDisableForNextMinutes(30)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (s *service) GetPortalLink(ctx context.Context, req *pb.RpcMembershipGetPortalLinkUrlRequest) (*pb.RpcMembershipGetPortalLinkUrlResponse, error) {
|
||||
// 1 - send request
|
||||
bsr := psp.GetSubscriptionPortalLinkRequest{
|
||||
// payment node will check if signature matches with this OwnerAnyID
|
||||
OwnerAnyId: s.wallet.Account().SignKey.GetPublic().Account(),
|
||||
}
|
||||
|
||||
payload, err := bsr.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
privKey := s.wallet.GetAccountPrivkey()
|
||||
signature, err := privKey.Sign(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqSigned := psp.GetSubscriptionPortalLinkRequestSigned{
|
||||
Payload: payload,
|
||||
Signature: signature,
|
||||
}
|
||||
|
||||
bsRet, err := s.ppclient.GetSubscriptionPortalLink(ctx, &reqSigned)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var out pb.RpcMembershipGetPortalLinkUrlResponse
|
||||
out.PortalUrl = bsRet.PortalUrl
|
||||
|
||||
// 2 - disable cache for 30 minutes
|
||||
log.Debug("disabling cache for 30 minutes after portal link was received")
|
||||
err = s.cache.CacheDisableForNextMinutes(30)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (s *service) GetVerificationEmail(ctx context.Context, req *pb.RpcMembershipGetVerificationEmailRequest) (*pb.RpcMembershipGetVerificationEmailResponse, error) {
|
||||
// 1 - send request
|
||||
bsr := psp.GetVerificationEmailRequest{
|
||||
// payment node will check if signature matches with this OwnerAnyID
|
||||
OwnerAnyId: s.wallet.Account().SignKey.GetPublic().Account(),
|
||||
Email: req.Email,
|
||||
SubscribeToNewsletter: req.SubscribeToNewsletter,
|
||||
}
|
||||
|
||||
payload, err := bsr.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
privKey := s.wallet.GetAccountPrivkey()
|
||||
signature, err := privKey.Sign(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqSigned := psp.GetVerificationEmailRequestSigned{
|
||||
Payload: payload,
|
||||
Signature: signature,
|
||||
}
|
||||
|
||||
_, err = s.ppclient.GetVerificationEmail(ctx, &reqSigned)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var out pb.RpcMembershipGetVerificationEmailResponse
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (s *service) VerifyEmailCode(ctx context.Context, req *pb.RpcMembershipVerifyEmailCodeRequest) (*pb.RpcMembershipVerifyEmailCodeResponse, error) {
|
||||
// 1 - send request
|
||||
bsr := psp.VerifyEmailRequest{
|
||||
// payment node will check if signature matches with this OwnerAnyID
|
||||
OwnerAnyId: s.wallet.Account().SignKey.GetPublic().Account(),
|
||||
OwnerEthAddress: s.wallet.GetAccountEthAddress().Hex(),
|
||||
Code: req.Code,
|
||||
}
|
||||
|
||||
payload, err := bsr.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
privKey := s.wallet.GetAccountPrivkey()
|
||||
signature, err := privKey.Sign(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqSigned := psp.VerifyEmailRequestSigned{
|
||||
Payload: payload,
|
||||
Signature: signature,
|
||||
}
|
||||
|
||||
// empty return or error
|
||||
_, err = s.ppclient.VerifyEmail(ctx, &reqSigned)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2 - clear cache
|
||||
log.Debug("clearing cache after email verification code was confirmed")
|
||||
err = s.cache.CacheClear()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// return out
|
||||
var out pb.RpcMembershipVerifyEmailCodeResponse
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (s *service) FinalizeSubscription(ctx context.Context, req *pb.RpcMembershipFinalizeRequest) (*pb.RpcMembershipFinalizeResponse, error) {
|
||||
// 1 - send request
|
||||
bsr := psp.FinalizeSubscriptionRequest{
|
||||
// payment node will check if signature matches with this OwnerAnyID
|
||||
OwnerAnyId: s.wallet.Account().SignKey.GetPublic().Account(),
|
||||
OwnerEthAddress: s.wallet.GetAccountEthAddress().Hex(),
|
||||
RequestedAnyName: req.RequestedAnyName,
|
||||
}
|
||||
|
||||
payload, err := bsr.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
privKey := s.wallet.GetAccountPrivkey()
|
||||
signature, err := privKey.Sign(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqSigned := psp.FinalizeSubscriptionRequestSigned{
|
||||
Payload: payload,
|
||||
Signature: signature,
|
||||
}
|
||||
|
||||
// empty return or error
|
||||
_, err = s.ppclient.FinalizeSubscription(ctx, &reqSigned)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2 - clear cache
|
||||
log.Debug("clearing cache after subscription was finalized")
|
||||
err = s.cache.CacheClear()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// return out
|
||||
var out pb.RpcMembershipFinalizeResponse
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (s *service) GetTiers(ctx context.Context, req *pb.RpcMembershipTiersGetRequest) (*pb.RpcMembershipTiersGetResponse, error) {
|
||||
// 1 - check in cache
|
||||
// status var. is unused here
|
||||
cachedStatus, cachedTiers, err := s.cache.CacheGet()
|
||||
|
||||
// if NoCache -> skip returning from cache
|
||||
if !req.NoCache && (err == nil) && (cachedTiers != nil) && (cachedTiers.Tiers != nil) {
|
||||
log.Debug("returning tiers from cache", zap.Error(err), zap.Any("cachedTiers", cachedTiers))
|
||||
return cachedTiers, nil
|
||||
}
|
||||
|
||||
// 2 - send request
|
||||
bsr := psp.GetTiersRequest{
|
||||
// payment node will check if signature matches with this OwnerAnyID
|
||||
OwnerAnyId: s.wallet.Account().SignKey.GetPublic().Account(),
|
||||
|
||||
// WARNING: we will save to cache data for THIS locale and payment method!!!
|
||||
Locale: req.Locale,
|
||||
PaymentMethod: req.PaymentMethod,
|
||||
}
|
||||
|
||||
payload, err := bsr.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
privKey := s.wallet.GetAccountPrivkey()
|
||||
signature, err := privKey.Sign(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqSigned := psp.GetTiersRequestSigned{
|
||||
Payload: payload,
|
||||
Signature: signature,
|
||||
}
|
||||
|
||||
// empty return or error
|
||||
tiers, err := s.ppclient.GetAllTiers(ctx, &reqSigned)
|
||||
if err != nil {
|
||||
// if error here -> we do not create empty array
|
||||
// with GetStatus above the logic is different
|
||||
// there we create empty status and save it to cache
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// return out
|
||||
var out pb.RpcMembershipTiersGetResponse
|
||||
|
||||
out.Tiers = make([]*model.MembershipTierData, len(tiers.Tiers))
|
||||
for i, tier := range tiers.Tiers {
|
||||
out.Tiers[i] = &model.MembershipTierData{
|
||||
Id: tier.Id,
|
||||
Name: tier.Name,
|
||||
Description: tier.Description,
|
||||
IsActive: tier.IsActive,
|
||||
IsTest: tier.IsTest,
|
||||
IsHiddenTier: tier.IsHiddenTier,
|
||||
PeriodType: model.MembershipTierDataPeriodType(tier.PeriodType),
|
||||
PeriodValue: tier.PeriodValue,
|
||||
PriceStripeUsdCents: tier.PriceStripeUsdCents,
|
||||
AnyNamesCountIncluded: tier.AnyNamesCountIncluded,
|
||||
AnyNameMinLength: tier.AnyNameMinLength,
|
||||
}
|
||||
|
||||
// copy all features
|
||||
out.Tiers[i].Features = make(map[string]*model.MembershipTierDataFeature)
|
||||
for k, v := range tier.Features {
|
||||
out.Tiers[i].Features[k] = &model.MembershipTierDataFeature{
|
||||
ValueStr: v.ValueStr,
|
||||
ValueUint: v.ValueUint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3 - update tiers, not status
|
||||
var cacheExpireTime time.Time
|
||||
if cachedStatus != nil {
|
||||
cacheExpireTime = time.Unix(int64(cachedStatus.Data.DateEnds), 0)
|
||||
} else {
|
||||
log.Debug("setting tiers cache to +1 day")
|
||||
|
||||
timeNow := time.Now().UTC()
|
||||
cacheExpireTime = timeNow.Add(1 * 24 * time.Hour)
|
||||
}
|
||||
|
||||
err = s.cache.CacheSet(nil, &out, cacheExpireTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &out, nil
|
||||
}
|
1071
core/payments/payments_test.go
Normal file
1071
core/payments/payments_test.go
Normal file
File diff suppressed because it is too large
Load diff
27
core/payments/utils.go
Normal file
27
core/payments/utils.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package payments
|
||||
|
||||
import (
|
||||
ens "github.com/wealdtech/go-ens/v3"
|
||||
)
|
||||
|
||||
func normalizeAnyName(name string) (string, error) {
|
||||
// 1. ENSIP1 standard: ens-go v3.6.0 (current) is using it
|
||||
// 2. ENSIP15 standard: that is an another standard for ENS namehashes
|
||||
// that was accepted in June 2023.
|
||||
//
|
||||
// Current AnyNS (as of February 2024) implementation support only ENSIP1
|
||||
//
|
||||
// https://eips.ethereum.org/EIPS/eip-137 (ENSIP1) grammar:
|
||||
// <domain> ::= <label> | <domain> "." <label>
|
||||
// <label> ::= any valid string label per [UTS46](https://unicode.org/reports/tr46/)
|
||||
//
|
||||
// "❶❷❸❹❺❻❼❽❾❿":
|
||||
// under ENSIP1 this OK
|
||||
// under ENSIP15 this is not OK, will fail
|
||||
name, err := ens.Normalize(name)
|
||||
if err != nil {
|
||||
return name, err
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
|
@ -6,8 +6,12 @@ import (
|
|||
app "github.com/anyproto/any-sync/app"
|
||||
accountdata "github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
|
||||
common "github.com/ethereum/go-ethereum/common"
|
||||
|
||||
crypto "github.com/anyproto/any-sync/util/crypto"
|
||||
|
||||
ecdsa "crypto/ecdsa"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
wallet "github.com/anyproto/anytype-heart/core/wallet"
|
||||
|
@ -73,6 +77,100 @@ func (_c *MockWallet_Account_Call) RunAndReturn(run func() *accountdata.AccountK
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetAccountEthAddress provides a mock function with given fields:
|
||||
func (_m *MockWallet) GetAccountEthAddress() common.Address {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetAccountEthAddress")
|
||||
}
|
||||
|
||||
var r0 common.Address
|
||||
if rf, ok := ret.Get(0).(func() common.Address); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(common.Address)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockWallet_GetAccountEthAddress_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAccountEthAddress'
|
||||
type MockWallet_GetAccountEthAddress_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetAccountEthAddress is a helper method to define mock.On call
|
||||
func (_e *MockWallet_Expecter) GetAccountEthAddress() *MockWallet_GetAccountEthAddress_Call {
|
||||
return &MockWallet_GetAccountEthAddress_Call{Call: _e.mock.On("GetAccountEthAddress")}
|
||||
}
|
||||
|
||||
func (_c *MockWallet_GetAccountEthAddress_Call) Run(run func()) *MockWallet_GetAccountEthAddress_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWallet_GetAccountEthAddress_Call) Return(_a0 common.Address) *MockWallet_GetAccountEthAddress_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWallet_GetAccountEthAddress_Call) RunAndReturn(run func() common.Address) *MockWallet_GetAccountEthAddress_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetAccountEthPrivkey provides a mock function with given fields:
|
||||
func (_m *MockWallet) GetAccountEthPrivkey() *ecdsa.PrivateKey {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetAccountEthPrivkey")
|
||||
}
|
||||
|
||||
var r0 *ecdsa.PrivateKey
|
||||
if rf, ok := ret.Get(0).(func() *ecdsa.PrivateKey); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*ecdsa.PrivateKey)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockWallet_GetAccountEthPrivkey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAccountEthPrivkey'
|
||||
type MockWallet_GetAccountEthPrivkey_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetAccountEthPrivkey is a helper method to define mock.On call
|
||||
func (_e *MockWallet_Expecter) GetAccountEthPrivkey() *MockWallet_GetAccountEthPrivkey_Call {
|
||||
return &MockWallet_GetAccountEthPrivkey_Call{Call: _e.mock.On("GetAccountEthPrivkey")}
|
||||
}
|
||||
|
||||
func (_c *MockWallet_GetAccountEthPrivkey_Call) Run(run func()) *MockWallet_GetAccountEthPrivkey_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWallet_GetAccountEthPrivkey_Call) Return(_a0 *ecdsa.PrivateKey) *MockWallet_GetAccountEthPrivkey_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockWallet_GetAccountEthPrivkey_Call) RunAndReturn(run func() *ecdsa.PrivateKey) *MockWallet_GetAccountEthPrivkey_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetAccountPrivkey provides a mock function with given fields:
|
||||
func (_m *MockWallet) GetAccountPrivkey() crypto.PrivKey {
|
||||
ret := _m.Called()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -10,6 +11,8 @@ import (
|
|||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||
|
||||
"github.com/anyproto/anytype-heart/metrics"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/logging"
|
||||
|
@ -20,6 +23,9 @@ const (
|
|||
keyFileDevice = "device.key"
|
||||
)
|
||||
|
||||
type EthPrivateKey = *ecdsa.PrivateKey
|
||||
type EthAddress = common.Address
|
||||
|
||||
type wallet struct {
|
||||
rootPath string
|
||||
repoPath string // other components will init their files/dirs inside
|
||||
|
@ -29,6 +35,11 @@ type wallet struct {
|
|||
deviceKey crypto.PrivKey
|
||||
masterKey crypto.PrivKey
|
||||
oldAccountKey crypto.PrivKey
|
||||
|
||||
// this key is used to sign ethereum transactions
|
||||
// and use Any Naming Service
|
||||
ethereumKey ecdsa.PrivateKey
|
||||
|
||||
// this is needed for any-sync
|
||||
accountData *accountdata.AccountKeys
|
||||
}
|
||||
|
@ -49,6 +60,20 @@ func (r *wallet) GetMasterKey() crypto.PrivKey {
|
|||
return r.masterKey
|
||||
}
|
||||
|
||||
func (r *wallet) GetAccountEthPrivkey() *ecdsa.PrivateKey {
|
||||
return &r.ethereumKey
|
||||
}
|
||||
|
||||
func (r *wallet) GetAccountEthAddress() EthAddress {
|
||||
publicKey := r.ethereumKey.Public()
|
||||
|
||||
// eat the error, we know it's an ecdsa.PublicKey
|
||||
publicKeyECDSA, _ := publicKey.(*ecdsa.PublicKey)
|
||||
ethAddress := ethcrypto.PubkeyToAddress(*publicKeyECDSA)
|
||||
|
||||
return common.HexToAddress(ethAddress.String())
|
||||
}
|
||||
|
||||
func (r *wallet) Init(a *app.App) (err error) {
|
||||
if r.accountKey == nil {
|
||||
return fmt.Errorf("no account key present")
|
||||
|
@ -114,6 +139,7 @@ func NewWithAccountRepo(rootPath string, derivationResult crypto.DerivationResul
|
|||
oldAccountKey: derivationResult.OldAccountKey,
|
||||
accountKey: derivationResult.Identity,
|
||||
deviceKeyPath: filepath.Join(repoPath, keyFileDevice),
|
||||
ethereumKey: derivationResult.EthereumIdentity,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,6 +164,10 @@ type Wallet interface {
|
|||
GetDevicePrivkey() crypto.PrivKey
|
||||
GetOldAccountKey() crypto.PrivKey
|
||||
GetMasterKey() crypto.PrivKey
|
||||
|
||||
GetAccountEthPrivkey() EthPrivateKey
|
||||
GetAccountEthAddress() EthAddress
|
||||
|
||||
ReadAppLink(appKey string) (*AppLinkPayload, error)
|
||||
PersistAppLink(payload *AppLinkPayload) (appKey string, err error)
|
||||
|
||||
|
|
3
doc/dependency_decisions.yml
Normal file
3
doc/dependency_decisions.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
- - :inherit_from
|
||||
- open/decisions.yml
|
1172
docs/proto.md
1172
docs/proto.md
File diff suppressed because it is too large
Load diff
11
go.mod
11
go.mod
|
@ -23,6 +23,7 @@ require (
|
|||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dsoprea/go-exif/v3 v3.0.1
|
||||
github.com/dsoprea/go-jpeg-image-structure/v2 v2.0.0-20210512043942-b434301c6836
|
||||
github.com/ethereum/go-ethereum v1.13.12
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/go-shiori/go-readability v0.0.0-20220215145315-dd6828d2f09b
|
||||
|
@ -81,6 +82,7 @@ require (
|
|||
github.com/uber/jaeger-client-go v2.30.0+incompatible
|
||||
github.com/valyala/fastjson v1.6.4
|
||||
github.com/vektra/mockery/v2 v2.38.0
|
||||
github.com/wealdtech/go-ens/v3 v3.6.0
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/yuin/goldmark v1.7.0
|
||||
go.uber.org/atomic v1.11.0
|
||||
|
@ -106,7 +108,9 @@ require (
|
|||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/RoaringBitmap/roaring v1.2.3 // indirect
|
||||
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
|
||||
github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
|
@ -144,6 +148,7 @@ require (
|
|||
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect
|
||||
github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect
|
||||
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
|
||||
github.com/deckarep/golang-set/v2 v2.1.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
|
||||
github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb // indirect
|
||||
|
@ -153,7 +158,6 @@ require (
|
|||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect
|
||||
github.com/ethereum/c-kzg-4844 v0.4.0 // indirect
|
||||
github.com/ethereum/go-ethereum v1.13.12 // indirect
|
||||
github.com/fogleman/gg v1.3.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
|
@ -174,6 +178,7 @@ require (
|
|||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/gosimple/unidecode v1.0.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect
|
||||
|
@ -233,8 +238,9 @@ require (
|
|||
github.com/quic-go/quic-go v0.40.1 // indirect
|
||||
github.com/rs/cors v1.7.0 // indirect
|
||||
github.com/rs/zerolog v1.29.0 // indirect
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/spf13/afero v1.10.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
|
@ -249,6 +255,7 @@ require (
|
|||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
|
||||
github.com/wealdtech/go-multicodec v1.4.0 // indirect
|
||||
github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
|
|
48
go.sum
48
go.sum
|
@ -54,6 +54,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q
|
|||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||
|
@ -197,6 +199,8 @@ github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq
|
|||
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
|
||||
github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
|
||||
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
|
@ -248,9 +252,11 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc
|
|||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/corona10/goimagehash v1.0.2 h1:pUfB0LnsJASMPGEZLj7tGY251vF+qLGqOgEP4rUs6kA=
|
||||
github.com/corona10/goimagehash v1.0.2/go.mod h1:/l9umBhvcHQXVtQO1V6Gp1yD20STawkhRnnX0D1bvVI=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg=
|
||||
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE=
|
||||
|
@ -271,6 +277,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4=
|
||||
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU=
|
||||
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U=
|
||||
github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI=
|
||||
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
|
||||
|
@ -337,6 +345,8 @@ github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1
|
|||
github.com/ethereum/go-ethereum v1.13.12 h1:iDr9UM2JWkngBHGovRJEQn4Kor7mT4gt9rUZqB5M29Y=
|
||||
github.com/ethereum/go-ethereum v1.13.12/go.mod h1:hKL2Qcj1OvStXNSEDbucexqnEt1Wh4Cz329XsjAalZY=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA=
|
||||
github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
|
||||
github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ=
|
||||
github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ=
|
||||
github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
|
||||
|
@ -353,6 +363,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
|
|||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
|
||||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
|
||||
github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE=
|
||||
github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
|
@ -385,6 +397,7 @@ github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
|||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
|
@ -433,6 +446,8 @@ github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7w
|
|||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
|
@ -500,6 +515,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
|||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
|
@ -568,6 +585,8 @@ github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN
|
|||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE=
|
||||
github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
|
@ -596,6 +615,10 @@ github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE
|
|||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/hbagdi/go-unsplash v0.0.0-20230414214043-474fc02c9119 h1:Xqi5LlXRyF1GlNGXSb2NZJuOeTrXGzwGiQDwkwNXEc8=
|
||||
github.com/hbagdi/go-unsplash v0.0.0-20230414214043-474fc02c9119/go.mod h1:DEzhU5CxSdknL3hUXTco1n5AO2BZHs4KeJo5ADWU0Iw=
|
||||
github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 h1:3JQNjnMRil1yD0IfZKHF9GxxWKDJGj8I0IqOUol//sw=
|
||||
github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc=
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
|
@ -1063,6 +1086,8 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F
|
|||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A=
|
||||
github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
|
||||
|
@ -1275,8 +1300,10 @@ github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
|||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
|
||||
github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
|
@ -1308,8 +1335,9 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
|||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
||||
|
@ -1350,6 +1378,8 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y
|
|||
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
|
||||
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
|
||||
github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc=
|
||||
github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA=
|
||||
github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
||||
|
@ -1400,7 +1430,10 @@ github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs
|
|||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk=
|
||||
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
||||
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
github.com/vektra/mockery/v2 v2.38.0 h1:I0LBuUzZHqAU4d1DknW0DTFBPO6n8TaD38WL2KJf3yI=
|
||||
|
@ -1412,6 +1445,12 @@ github.com/warpfork/go-wish v0.0.0-20190328234359-8b3e70f8e830/go.mod h1:x6AKhvS
|
|||
github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
|
||||
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=
|
||||
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
|
||||
github.com/wealdtech/go-ens/v3 v3.6.0 h1:EAByZlHRQ3vxqzzwNi0GvEq1AjVozfWO4DMldHcoVg8=
|
||||
github.com/wealdtech/go-ens/v3 v3.6.0/go.mod h1:hcmMr9qPoEgVSEXU2Bwzrn/9NczTWZ1rE53jIlqUpzw=
|
||||
github.com/wealdtech/go-multicodec v1.4.0 h1:iq5PgxwssxnXGGPTIK1srvt6U5bJwIp7k6kBrudIWxg=
|
||||
github.com/wealdtech/go-multicodec v1.4.0/go.mod h1:aedGMaTeYkIqi/KCPre1ho5rTb3hGpu/snBOS3GQLw4=
|
||||
github.com/wealdtech/go-string2eth v1.2.1 h1:u9sofvGFkp+uvTg4Nvsvy5xBaiw8AibGLLngfC4F76g=
|
||||
github.com/wealdtech/go-string2eth v1.2.1/go.mod h1:9uwxm18zKZfrReXrGIbdiRYJtbE91iGcj6TezKKEx80=
|
||||
github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E=
|
||||
github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8=
|
||||
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc=
|
||||
|
@ -1431,6 +1470,8 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
|
|||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
@ -1749,6 +1790,7 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -1791,6 +1833,8 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb
|
|||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@ -1994,6 +2038,8 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
|
|||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8=
|
||||
gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE=
|
||||
|
|
11995
pb/commands.pb.go
11995
pb/commands.pb.go
File diff suppressed because it is too large
Load diff
1092
pb/events.pb.go
1092
pb/events.pb.go
File diff suppressed because it is too large
Load diff
|
@ -6356,6 +6356,406 @@ message Rpc {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Membership is a bundle of several "Features"
|
||||
* every user should have one and only one tier
|
||||
* users can not have N tiers (no combining)
|
||||
*/
|
||||
message Membership {
|
||||
/**
|
||||
* Get the current status of the membership
|
||||
* including the tier, status, dates, etc
|
||||
* WARNING: this can be cached by Anytype heart
|
||||
*/
|
||||
message GetStatus {
|
||||
message Request {
|
||||
// pass true to force the cache update
|
||||
// by default this is false
|
||||
bool noCache = 1;
|
||||
}
|
||||
|
||||
message Response {
|
||||
Error error = 1;
|
||||
|
||||
anytype.model.Membership data = 2;
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
string description = 2;
|
||||
|
||||
enum Code {
|
||||
NULL = 0;
|
||||
UNKNOWN_ERROR = 1;
|
||||
BAD_INPUT = 2;
|
||||
|
||||
NOT_LOGGED_IN = 3;
|
||||
PAYMENT_NODE_ERROR = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the requested name is valid for the requested tier
|
||||
* before requesting a payment link and paying
|
||||
*/
|
||||
message IsNameValid {
|
||||
message Request {
|
||||
int32 requestedTier = 1;
|
||||
// full name including .any suffix
|
||||
string requestedAnyName = 2;
|
||||
}
|
||||
|
||||
message Response {
|
||||
Error error = 1;
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
string description = 2;
|
||||
|
||||
enum Code {
|
||||
NULL = 0;
|
||||
UNKNOWN_ERROR = 1;
|
||||
BAD_INPUT = 2;
|
||||
|
||||
TOO_SHORT = 3;
|
||||
TOO_LONG = 4;
|
||||
HAS_INVALID_CHARS = 5;
|
||||
TIER_FEATURES_NO_NAME = 6;
|
||||
// if everything is fine - "name is already taken" check should be done in the NS
|
||||
// see IsNameAvailable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a link to the payment provider
|
||||
* where user can pay for the membership
|
||||
*/
|
||||
message GetPaymentUrl {
|
||||
message Request {
|
||||
int32 requestedTier = 1;
|
||||
|
||||
anytype.model.Membership.PaymentMethod paymentMethod = 2;
|
||||
|
||||
// if empty - then no name requested
|
||||
// if non-empty - PP node will register that name on behalf of the user
|
||||
string requestedAnyName = 3;
|
||||
}
|
||||
|
||||
message Response {
|
||||
Error error = 1;
|
||||
|
||||
// will feature current billing ID
|
||||
// stripe.com/?client_reference_id=1234
|
||||
string paymentUrl = 2;
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
string description = 2;
|
||||
|
||||
enum Code {
|
||||
NULL = 0;
|
||||
UNKNOWN_ERROR = 1;
|
||||
BAD_INPUT = 2;
|
||||
|
||||
NOT_LOGGED_IN = 3;
|
||||
PAYMENT_NODE_ERROR = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a link to the portal where user can:
|
||||
* a) change his billing details
|
||||
* b) see payment info, invoices, etc
|
||||
* c) cancel membership
|
||||
*/
|
||||
message GetPortalLinkUrl {
|
||||
message Request {
|
||||
|
||||
}
|
||||
|
||||
message Response {
|
||||
Error error = 1;
|
||||
|
||||
string portalUrl = 2;
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
string description = 2;
|
||||
|
||||
enum Code {
|
||||
NULL = 0;
|
||||
UNKNOWN_ERROR = 1;
|
||||
BAD_INPUT = 2;
|
||||
|
||||
NOT_LOGGED_IN = 3;
|
||||
PAYMENT_NODE_ERROR = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message Finalize {
|
||||
message Request {
|
||||
// if empty - then no name requested
|
||||
// if non-empty - PP node will register that name on behalf of the user
|
||||
string requestedAnyName = 1;
|
||||
}
|
||||
|
||||
message Response {
|
||||
Error error = 1;
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
string description = 2;
|
||||
|
||||
enum Code {
|
||||
NULL = 0;
|
||||
UNKNOWN_ERROR = 1;
|
||||
BAD_INPUT = 2;
|
||||
|
||||
NOT_LOGGED_IN = 3;
|
||||
PAYMENT_NODE_ERROR = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an e-mail with verification code to the user
|
||||
* can be called multiple times but with some timeout (N seconds) between calls
|
||||
*/
|
||||
message GetVerificationEmail {
|
||||
message Request {
|
||||
string email = 1;
|
||||
|
||||
bool subscribeToNewsletter = 2;
|
||||
}
|
||||
|
||||
message Response {
|
||||
Error error = 1;
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
string description = 2;
|
||||
|
||||
enum Code {
|
||||
NULL = 0;
|
||||
UNKNOWN_ERROR = 1;
|
||||
BAD_INPUT = 2;
|
||||
|
||||
NOT_LOGGED_IN = 3;
|
||||
PAYMENT_NODE_ERROR = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the e-mail address of the user
|
||||
* need a correct code that was sent to the user when calling GetVerificationEmail
|
||||
*/
|
||||
message VerifyEmailCode {
|
||||
message Request {
|
||||
string code = 1;
|
||||
}
|
||||
|
||||
message Response {
|
||||
Error error = 1;
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
string description = 2;
|
||||
|
||||
enum Code {
|
||||
NULL = 0;
|
||||
UNKNOWN_ERROR = 1;
|
||||
BAD_INPUT = 2;
|
||||
|
||||
NOT_LOGGED_IN = 3;
|
||||
PAYMENT_NODE_ERROR = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tiers can change on the backend so if you want to show users the latest data
|
||||
* you can call this method to get the latest tiers
|
||||
*/
|
||||
message Tiers {
|
||||
message Get {
|
||||
message Request {
|
||||
// pass true to force the cache update
|
||||
// by default this is false
|
||||
bool noCache = 1;
|
||||
|
||||
string locale = 2;
|
||||
|
||||
uint32 paymentMethod = 3;
|
||||
}
|
||||
|
||||
message Response {
|
||||
Error error = 1;
|
||||
repeated anytype.model.MembershipTierData tiers = 2;
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
string description = 2;
|
||||
|
||||
enum Code {
|
||||
NULL = 0;
|
||||
UNKNOWN_ERROR = 1;
|
||||
BAD_INPUT = 2;
|
||||
|
||||
NOT_LOGGED_IN = 3;
|
||||
PAYMENT_NODE_ERROR = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message NameService {
|
||||
message ResolveName {
|
||||
message Request {
|
||||
// including ".any" suffix
|
||||
string fullName = 1;
|
||||
}
|
||||
|
||||
message Response {
|
||||
Error error = 1;
|
||||
|
||||
bool available = 2;
|
||||
|
||||
// EOA -> SCW -> name
|
||||
// This field is non-empty only if name is "already registered"
|
||||
string ownerScwEthAddress = 3;
|
||||
|
||||
// This field is non-empty only if name is "already registered"
|
||||
string ownerEthAddress = 4;
|
||||
|
||||
// A content hash attached to this name
|
||||
// This field is non-empty only if name is "already registered"
|
||||
string ownerAnyAddress = 5;
|
||||
|
||||
// A SpaceId attached to this name
|
||||
// This field is non-empty only if name is "already registered"
|
||||
string spaceId = 6;
|
||||
|
||||
// A timestamp when this name expires
|
||||
int64 nameExpires = 7;
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
string description = 2;
|
||||
|
||||
enum Code {
|
||||
NULL = 0;
|
||||
UNKNOWN_ERROR = 1;
|
||||
BAD_INPUT = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message ResolveAnyId {
|
||||
message Request {
|
||||
string anyId = 1;
|
||||
}
|
||||
|
||||
message Response {
|
||||
Error error = 1;
|
||||
|
||||
bool found = 2;
|
||||
|
||||
// including ".any" suffix
|
||||
string fullName = 3;
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
string description = 2;
|
||||
|
||||
enum Code {
|
||||
NULL = 0;
|
||||
UNKNOWN_ERROR = 1;
|
||||
BAD_INPUT = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message ResolveSpaceId {
|
||||
message Request {
|
||||
string spaceId = 1;
|
||||
}
|
||||
|
||||
message Response {
|
||||
Error error = 1;
|
||||
|
||||
bool found = 2;
|
||||
|
||||
// including ".any" suffix
|
||||
string fullName = 3;
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
string description = 2;
|
||||
|
||||
enum Code {
|
||||
NULL = 0;
|
||||
UNKNOWN_ERROR = 1;
|
||||
BAD_INPUT = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message UserAccount {
|
||||
message Get {
|
||||
message Request {
|
||||
|
||||
}
|
||||
|
||||
message Response {
|
||||
Error error = 1;
|
||||
|
||||
// this will use ReverseResolve to get current name
|
||||
// user can buy many names, but
|
||||
// only 1 name can be set as "current": ETH address <-> name
|
||||
string anyNameAttached = 2;
|
||||
|
||||
// Number of names that the user can reserve
|
||||
uint64 namesCountLeft = 3;
|
||||
|
||||
// Number of operations: update name, add new data, etc
|
||||
uint64 operationsCountLeft = 4;
|
||||
|
||||
// TODO: all operations list
|
||||
|
||||
message Error {
|
||||
Code code = 1;
|
||||
string description = 2;
|
||||
|
||||
enum Code {
|
||||
NULL = 0;
|
||||
UNKNOWN_ERROR = 1;
|
||||
BAD_INPUT = 2;
|
||||
NOT_LOGGED_IN = 3;
|
||||
BAD_NAME_RESOLVE = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message Broadcast {
|
||||
message PayloadEvent {
|
||||
message Request {
|
||||
|
@ -6381,6 +6781,8 @@ message Rpc {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
message Empty {
|
||||
|
||||
}
|
||||
|
|
|
@ -100,6 +100,8 @@ message Event {
|
|||
Notification.Update notificationUpdate = 115;
|
||||
|
||||
Payload.Broadcast payloadBroadcast = 116;
|
||||
|
||||
Membership.Update membershipUpdate = 117;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1034,6 +1036,12 @@ message Event {
|
|||
}
|
||||
}
|
||||
|
||||
message Membership {
|
||||
message Update {
|
||||
anytype.model.Membership data = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message Notification {
|
||||
message Send {
|
||||
anytype.model.Notification notification = 1;
|
||||
|
|
|
@ -310,5 +310,36 @@ service ClientCommands {
|
|||
rpc NotificationReply (anytype.Rpc.Notification.Reply.Request) returns (anytype.Rpc.Notification.Reply.Response);
|
||||
rpc NotificationTest (anytype.Rpc.Notification.Test.Request) returns (anytype.Rpc.Notification.Test.Response);
|
||||
|
||||
// Membership
|
||||
// ***
|
||||
// Get current subscription status (tier, expiration date, etc.)
|
||||
// WARNING: can be cached by Anytype Heart
|
||||
rpc MembershipGetStatus (anytype.Rpc.Membership.GetStatus.Request) returns (anytype.Rpc.Membership.GetStatus.Response);
|
||||
rpc MembershipIsNameValid( anytype.Rpc.Membership.IsNameValid.Request) returns (anytype.Rpc.Membership.IsNameValid.Response);
|
||||
// Buy a subscription, will return a payment URL. The user should be redirected to this URL to complete the payment.
|
||||
rpc MembershipGetPaymentUrl (anytype.Rpc.Membership.GetPaymentUrl.Request) returns (anytype.Rpc.Membership.GetPaymentUrl.Response);
|
||||
// Get a link to the user's subscription management portal. The user should be redirected to this URL to manage their subscription:
|
||||
// a) change his billing details
|
||||
// b) see payment info, invoices, etc
|
||||
// c) cancel the subscription
|
||||
rpc MembershipGetPortalLinkUrl (anytype.Rpc.Membership.GetPortalLinkUrl.Request) returns (anytype.Rpc.Membership.GetPortalLinkUrl.Response);
|
||||
// Send a verification code to the user's email. The user should enter this code to verify his email.
|
||||
rpc MembershipGetVerificationEmail (anytype.Rpc.Membership.GetVerificationEmail.Request) returns (anytype.Rpc.Membership.GetVerificationEmail.Response);
|
||||
// Verify the user's email with the code received in the previous step (MembershipGetVerificationEmail)
|
||||
rpc MembershipVerifyEmailCode (anytype.Rpc.Membership.VerifyEmailCode.Request) returns (anytype.Rpc.Membership.VerifyEmailCode.Response);
|
||||
// If your subscription is in PendingRequiresFinalization:
|
||||
// please call MembershipFinalize to finish the process
|
||||
rpc MembershipFinalize (anytype.Rpc.Membership.Finalize.Request) returns (anytype.Rpc.Membership.Finalize.Response);
|
||||
|
||||
rpc MembershipGetTiers (anytype.Rpc.Membership.Tiers.Get.Request) returns (anytype.Rpc.Membership.Tiers.Get.Response);
|
||||
|
||||
// Name Service:
|
||||
// ***
|
||||
// hello.any -> data
|
||||
rpc NameServiceUserAccountGet( anytype.Rpc.NameService.UserAccount.Get.Request) returns (anytype.Rpc.NameService.UserAccount.Get.Response);
|
||||
rpc NameServiceResolveName( anytype.Rpc.NameService.ResolveName.Request) returns (anytype.Rpc.NameService.ResolveName.Response);
|
||||
// 12D3KooWA8EXV3KjBxEU5EnsPfneLx84vMWAtTBQBeyooN82KSuS -> hello.any
|
||||
rpc NameServiceResolveAnyId( anytype.Rpc.NameService.ResolveAnyId.Request ) returns (anytype.Rpc.NameService.ResolveAnyId.Response);
|
||||
|
||||
rpc BroadcastPayloadEvent (anytype.Rpc.Broadcast.PayloadEvent.Request) returns (anytype.Rpc.Broadcast.PayloadEvent.Response);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -180,6 +180,7 @@ const (
|
|||
RelationKeyRevision domain.RelationKey = "revision"
|
||||
RelationKeyImageKind domain.RelationKey = "imageKind"
|
||||
RelationKeyImportType domain.RelationKey = "importType"
|
||||
RelationKeyGlobalName domain.RelationKey = "globalName"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -856,6 +857,19 @@ var (
|
|||
ReadOnlyRelation: true,
|
||||
Scope: model.Relation_type,
|
||||
},
|
||||
RelationKeyGlobalName: {
|
||||
|
||||
DataSource: model.Relation_details,
|
||||
Description: "Name of profile that the user could be mentioned by",
|
||||
Format: model.RelationFormat_shorttext,
|
||||
Id: "_brglobalName",
|
||||
Key: "globalName",
|
||||
MaxCount: 1,
|
||||
Name: "Global name",
|
||||
ReadOnly: true,
|
||||
ReadOnlyRelation: true,
|
||||
Scope: model.Relation_type,
|
||||
},
|
||||
RelationKeyHeightInPixels: {
|
||||
|
||||
DataSource: model.Relation_details,
|
||||
|
|
|
@ -1688,5 +1688,15 @@
|
|||
"name": "Import Type",
|
||||
"readonly": true,
|
||||
"source": "details"
|
||||
},
|
||||
{
|
||||
"description": "Name of profile that the user could be mentioned by",
|
||||
"format": "shorttext",
|
||||
"hidden": false,
|
||||
"key": "globalName",
|
||||
"maxCount": 1,
|
||||
"name": "Global name",
|
||||
"readonly": true,
|
||||
"source": "details"
|
||||
}
|
||||
]
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1117,6 +1117,7 @@ message IdentityProfile {
|
|||
string iconCid = 3;
|
||||
repeated FileEncryptionKey iconEncryptionKeys = 4;
|
||||
string description = 5;
|
||||
string globalName = 6;
|
||||
}
|
||||
|
||||
message FileInfo {
|
||||
|
@ -1155,3 +1156,110 @@ message ManifestInfo {
|
|||
repeated string categories = 11;
|
||||
string language = 12;
|
||||
}
|
||||
|
||||
message Membership {
|
||||
enum Tier {
|
||||
TierNewUser = 0;
|
||||
// "free" tier
|
||||
TierExplorer = 1;
|
||||
// this tier can be used just for testing in debug mode
|
||||
// it will still create an active subscription, but with NO features
|
||||
TierBuilder1WeekTEST = 2;
|
||||
// this tier can be used just for testing in debug mode
|
||||
// it will still create an active subscription, but with NO features
|
||||
TierCoCreator1WeekTEST = 3;
|
||||
TierBuilder = 4;
|
||||
TierCoCreator = 5;
|
||||
}
|
||||
|
||||
enum Status {
|
||||
StatusUnknown = 0;
|
||||
// please wait a bit more
|
||||
StatusPending = 1;
|
||||
StatusActive = 2;
|
||||
// in some cases we need to finalize the process:
|
||||
// - if user has bought membership directly without first calling
|
||||
// the BuySubscription method
|
||||
//
|
||||
// in this case please call Finalize to finish the process
|
||||
StatusPendingRequiresFinalization = 3;
|
||||
}
|
||||
|
||||
enum PaymentMethod {
|
||||
MethodCard = 0;
|
||||
MethodCrypto = 1;
|
||||
MethodApplePay = 2;
|
||||
MethodGooglePay = 3;
|
||||
MethodAppleInapp = 4;
|
||||
MethodGoogleInapp = 5;
|
||||
}
|
||||
|
||||
// it was Tier before, changed to int32 to allow dynamic values
|
||||
int32 tier = 1;
|
||||
Status status = 2;
|
||||
uint64 dateStarted = 3;
|
||||
uint64 dateEnds = 4;
|
||||
bool isAutoRenew = 5;
|
||||
PaymentMethod paymentMethod = 6;
|
||||
// can be empty if user did not ask for any name
|
||||
string requestedAnyName = 7;
|
||||
// if the email was verified by the user or set during the checkout - it will be here
|
||||
string userEmail = 8;
|
||||
bool subscribeToNewsletter = 9;
|
||||
}
|
||||
|
||||
message MembershipTierData {
|
||||
enum PeriodType {
|
||||
PeriodTypeUnknown = 0;
|
||||
PeriodTypeUnlimited = 1;
|
||||
PeriodTypeDays = 2;
|
||||
PeriodTypeWeeks = 3;
|
||||
PeriodTypeMonths = 4;
|
||||
PeriodTypeYears = 5;
|
||||
}
|
||||
|
||||
message Feature {
|
||||
// usually feature has uint value
|
||||
// like "storage" - 120
|
||||
uint32 valueUint = 1;
|
||||
|
||||
// in case feature will have string value
|
||||
string valueStr = 2;
|
||||
}
|
||||
|
||||
// this is a unique ID of the tier
|
||||
// you should hardcode this in your app and provide icon, graphics, etc for each tier
|
||||
// (even for old/historical/inactive/hidden tiers)
|
||||
uint32 id = 1;
|
||||
// localazied name of the tier
|
||||
string name = 2;
|
||||
// just a short technical description
|
||||
// you don't have to use it, you can use your own UI-friendly texts
|
||||
string description = 3;
|
||||
// can you buy it (ON ALL PLATFORMS, without clarification)?
|
||||
bool isActive = 4;
|
||||
// is this tier for debugging only?
|
||||
bool isTest = 5;
|
||||
// hidden tiers are only visible once user got them
|
||||
bool isHiddenTier = 6;
|
||||
// how long is the period of the subscription
|
||||
PeriodType periodType = 7;
|
||||
// i.e. "5 days" or "3 years"
|
||||
uint32 periodValue = 8;
|
||||
// this one is a price we use ONLY on Stripe platform
|
||||
uint32 priceStripeUsdCents = 9;
|
||||
// number of ANY NS names that this tier includes
|
||||
// (not counted as a "feature" and not in the features list)
|
||||
uint32 anyNamesCountIncluded = 10;
|
||||
// somename.any - len of 8
|
||||
uint32 anyNameMinLength = 11;
|
||||
// each tier has a set of features
|
||||
// each feature has a unique key: "storage", "invites", etc
|
||||
// not using enum here to provide dynamic feature list:
|
||||
//
|
||||
// "stoageGB" -> {64, ""}
|
||||
// "invites" -> {120, ""}
|
||||
// "spaces-public" -> {10, ""}
|
||||
// ...
|
||||
map<string, Feature> features = 12;
|
||||
}
|
|
@ -184,6 +184,7 @@ func (a *aclObjectManager) initAndRegisterMyIdentity(ctx context.Context) error
|
|||
details.Fields[bundle.RelationKeyDescription.String()] = pbtypes.String(pbtypes.GetString(profileDetails, bundle.RelationKeyDescription.String()))
|
||||
details.Fields[bundle.RelationKeyIconImage.String()] = pbtypes.String(pbtypes.GetString(profileDetails, bundle.RelationKeyIconImage.String()))
|
||||
details.Fields[bundle.RelationKeyIdentityProfileLink.String()] = pbtypes.String(pbtypes.GetString(profileDetails, bundle.RelationKeyId.String()))
|
||||
details.Fields[bundle.RelationKeyGlobalName.String()] = pbtypes.String(pbtypes.GetString(profileDetails, bundle.RelationKeyGlobalName.String()))
|
||||
err = a.modifier.ModifyDetails(id, func(current *types.Struct) (*types.Struct, error) {
|
||||
return pbtypes.StructMerge(current, details, false), nil
|
||||
})
|
||||
|
@ -339,6 +340,7 @@ func (a *aclObjectManager) updateParticipantFromIdentity(ctx context.Context, id
|
|||
bundle.RelationKeyName.String(): pbtypes.String(profile.Name),
|
||||
bundle.RelationKeyDescription.String(): pbtypes.String(profile.Description),
|
||||
bundle.RelationKeyIconImage.String(): pbtypes.String(profile.IconCid),
|
||||
bundle.RelationKeyGlobalName.String(): pbtypes.String(profile.GlobalName),
|
||||
}}
|
||||
return a.modifier.ModifyDetails(id, func(current *types.Struct) (*types.Struct, error) {
|
||||
return pbtypes.StructMerge(current, details, false), nil
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue