1
0
Fork 0
mirror of https://github.com/anyproto/anytype-heart.git synced 2025-06-08 05:47:07 +09:00

GO-4400 Refactor invites

This commit is contained in:
Mikhail Rakhmanov 2025-05-14 20:45:38 +02:00
parent dccb0eb469
commit b8ab62fa04
No known key found for this signature in database
GPG key ID: DED12CFEF5B8396B
25 changed files with 2213 additions and 2440 deletions

View file

@ -123,9 +123,6 @@ packages:
github.com/anyproto/anytype-heart/space/internal/components/participantwatcher:
interfaces:
ParticipantWatcher:
github.com/anyproto/anytype-heart/space/internal/components/invitemigrator:
interfaces:
InviteMigrator:
github.com/anyproto/anytype-heart/space/internal/components/aclnotifications:
interfaces:
AclNotification:

View file

@ -46,7 +46,7 @@ type AccountPermissions struct {
type AclService interface {
app.Component
GenerateInvite(ctx context.Context, spaceId string) (domain.InviteInfo, error)
GenerateInvite(ctx context.Context, spaceId string, inviteType model.InviteType, permissions model.ParticipantPermissions) (domain.InviteInfo, error)
RevokeInvite(ctx context.Context, spaceId string) error
GetCurrentInvite(ctx context.Context, spaceId string) (domain.InviteInfo, error)
GetGuestUserInvite(ctx context.Context, spaceId string) (domain.InviteInfo, error)
@ -562,26 +562,40 @@ func (a *aclService) GetCurrentInvite(ctx context.Context, spaceId string) (doma
return a.inviteService.GetCurrent(ctx, spaceId)
}
func (a *aclService) GenerateInvite(ctx context.Context, spaceId string) (result domain.InviteInfo, err error) {
func (a *aclService) GenerateInvite(ctx context.Context, spaceId string, invType model.InviteType, permissions model.ParticipantPermissions) (result domain.InviteInfo, err error) {
if spaceId == a.accountService.PersonalSpaceID() {
err = ErrPersonalSpace
return
}
var (
inviteExists = false
inviteType = domain.InviteType(invType)
)
current, err := a.inviteService.GetCurrent(ctx, spaceId)
if err == nil {
return current, nil
inviteExists = true
if current.InviteType == inviteType {
return current, nil
}
}
acceptSpace, err := a.spaceService.Get(ctx, spaceId)
if err != nil {
return
}
aclClient := acceptSpace.CommonSpace().AclClient()
res, err := aclClient.GenerateInvite()
aclPermissions := domain.ConvertParticipantPermissions(permissions)
res, err := aclClient.GenerateInvite(inviteExists, inviteType == domain.InviteTypeDefault, aclPermissions)
if err != nil {
err = convertedOrInternalError("couldn't generate acl invite", err)
return
}
return a.inviteService.Generate(ctx, spaceId, res.InviteKey, func() error {
params := inviteservice.GenerateInviteParams{
SpaceId: spaceId,
Key: res.InviteKey,
InviteType: inviteType,
Permissions: aclPermissions,
}
return a.inviteService.Generate(ctx, params, func() error {
err := aclClient.AddRecord(ctx, res.InviteRec)
if err != nil {
return convertedOrAclRequestError(err)

View file

@ -32,8 +32,6 @@ var spaceViewRequiredRelations = []domain.RelationKey{
bundle.RelationKeySpaceLocalStatus,
bundle.RelationKeySpaceRemoteStatus,
bundle.RelationKeyTargetSpaceId,
bundle.RelationKeySpaceInviteFileCid,
bundle.RelationKeySpaceInviteFileKey,
bundle.RelationKeyIsAclShared,
bundle.RelationKeySharedSpacesLimit,
bundle.RelationKeySpaceAccountStatus,
@ -121,28 +119,6 @@ func (s *SpaceView) initTemplate(st *state.State) {
)
}
func (s *SpaceView) GetExistingInviteInfo() (fileCid string, fileKey string) {
details := s.CombinedDetails()
fileCid = details.GetString(bundle.RelationKeySpaceInviteFileCid)
fileKey = details.GetString(bundle.RelationKeySpaceInviteFileKey)
return
}
func (s *SpaceView) RemoveExistingInviteInfo() (fileCid string, err error) {
details := s.Details()
fileCid = details.GetString(bundle.RelationKeySpaceInviteFileCid)
newState := s.NewState()
newState.RemoveDetail(bundle.RelationKeySpaceInviteFileCid, bundle.RelationKeySpaceInviteFileKey)
return fileCid, s.Apply(newState)
}
func (s *SpaceView) GetGuestUserInviteInfo() (fileCid string, fileKey string) {
details := s.CombinedDetails()
fileCid = details.GetString(bundle.RelationKeySpaceInviteGuestFileCid)
fileKey = details.GetString(bundle.RelationKeySpaceInviteGuestFileKey)
return
}
func (s *SpaceView) TryClose(objectTTL time.Duration) (res bool, err error) {
return false, nil
}
@ -210,13 +186,6 @@ func (s *SpaceView) GetSharedSpacesLimit() (limit int) {
return int(s.CombinedDetails().GetInt64(bundle.RelationKeySharedSpacesLimit))
}
func (s *SpaceView) SetInviteFileInfo(fileCid string, fileKey string) (err error) {
st := s.NewState()
st.SetDetailAndBundledRelation(bundle.RelationKeySpaceInviteFileCid, domain.String(fileCid))
st.SetDetailAndBundledRelation(bundle.RelationKeySpaceInviteFileKey, domain.String(fileKey))
return s.Apply(st)
}
func (s *SpaceView) afterApply(info smartblock.ApplyInfo) (err error) {
s.spaceService.OnViewUpdated(s.getSpacePersistentInfo(info.State))
return nil

View file

@ -95,17 +95,21 @@ func (w *Workspaces) CreationStateMigration(ctx *smartblock.InitContext) migrati
}
}
func (w *Workspaces) SetInviteFileInfo(fileCid string, fileKey string) (err error) {
func (w *Workspaces) SetInviteFileInfo(info domain.InviteInfo) (err error) {
st := w.NewState()
st.SetDetailAndBundledRelation(bundle.RelationKeySpaceInviteFileCid, domain.String(fileCid))
st.SetDetailAndBundledRelation(bundle.RelationKeySpaceInviteFileKey, domain.String(fileKey))
st.SetDetailAndBundledRelation(bundle.RelationKeySpaceInvitePermissions, domain.Int64(domain.ConvertAclPermissions(info.Permissions)))
st.SetDetailAndBundledRelation(bundle.RelationKeySpaceInviteType, domain.Int64(info.InviteType))
st.SetDetailAndBundledRelation(bundle.RelationKeySpaceInviteFileCid, domain.String(info.InviteFileCid))
st.SetDetailAndBundledRelation(bundle.RelationKeySpaceInviteFileKey, domain.String(info.InviteFileKey))
return w.Apply(st)
}
func (w *Workspaces) GetExistingInviteInfo() (fileCid string, fileKey string) {
func (w *Workspaces) GetExistingInviteInfo() (inviteInfo domain.InviteInfo) {
details := w.CombinedDetails()
fileCid = details.GetString(bundle.RelationKeySpaceInviteFileCid)
fileKey = details.GetString(bundle.RelationKeySpaceInviteFileKey)
inviteInfo.InviteType = domain.InviteType(details.GetInt64(bundle.RelationKeySpaceInviteType))
inviteInfo.Permissions = domain.ConvertParticipantPermissions(model.ParticipantPermissions(details.GetInt64(bundle.RelationKeySpaceInviteType)))
inviteInfo.InviteFileCid = details.GetString(bundle.RelationKeySpaceInviteFileCid)
inviteInfo.InviteFileKey = details.GetString(bundle.RelationKeySpaceInviteFileKey)
return
}

View file

@ -1,5 +1,12 @@
package domain
import (
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
)
type InviteView struct {
SpaceId string
SpaceName string
@ -16,16 +23,50 @@ func (i InviteView) IsGuestUserInvite() bool {
return false
}
type InviteType int
const (
InviteTypeDefault InviteType = iota
InviteTypeGuest
InviteTypeAnyone
)
type InviteInfo struct {
InviteFileCid string
InviteFileKey string
InviteType InviteType
Permissions list.AclPermissions
}
type InviteObject interface {
SetInviteFileInfo(fileCid string, fileKey string) (err error)
GetExistingInviteInfo() (fileCid string, fileKey string)
RemoveExistingInviteInfo() (fileCid string, err error)
SetInviteFileInfo(inviteInfo InviteInfo) (err error)
GetExistingInviteInfo() InviteInfo
RemoveExistingInviteInfo() (InviteInfo, error)
SetGuestInviteFileInfo(fileCid string, fileKey string) (err error)
GetExistingGuestInviteInfo() (fileCid string, fileKey string)
}
func ConvertParticipantPermissions(permissions model.ParticipantPermissions) list.AclPermissions {
switch permissions {
case model.ParticipantPermissions_Writer:
return list.AclPermissionsWriter
case model.ParticipantPermissions_Reader:
return list.AclPermissionsReader
case model.ParticipantPermissions_Owner:
return list.AclPermissionsOwner
}
return list.AclPermissionsNone
}
func ConvertAclPermissions(permissions list.AclPermissions) model.ParticipantPermissions {
switch aclrecordproto.AclUserPermissions(permissions) {
case aclrecordproto.AclUserPermissions_Writer:
return model.ParticipantPermissions_Writer
case aclrecordproto.AclUserPermissions_Reader:
return model.ParticipantPermissions_Reader
case aclrecordproto.AclUserPermissions_Owner:
return model.ParticipantPermissions_Owner
}
return model.ParticipantPermissions_NoPermissions
}

View file

@ -6,6 +6,7 @@ import (
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace/object/acl/list"
"github.com/anyproto/any-sync/util/crypto"
"github.com/gogo/protobuf/proto"
"github.com/ipfs/go-cid"
@ -32,7 +33,7 @@ type InviteService interface {
GetPayload(ctx context.Context, inviteCid cid.Cid, inviteFileKey crypto.SymKey) (*model.InvitePayload, error)
View(ctx context.Context, inviteCid cid.Cid, inviteFileKey crypto.SymKey) (domain.InviteView, error)
RemoveExisting(ctx context.Context, spaceId string) error
Generate(ctx context.Context, spaceId string, inviteKey crypto.PrivKey, sendInvite func() error) (domain.InviteInfo, error)
Generate(ctx context.Context, params GenerateInviteParams, sendInvite func() error) (domain.InviteInfo, error)
GetCurrent(ctx context.Context, spaceId string) (domain.InviteInfo, error)
GetExistingGuestUserInvite(ctx context.Context, spaceId string) (domain.InviteInfo, error)
GenerateGuestUserInvite(ctx context.Context, spaceId string, guestKey crypto.PrivKey) (domain.InviteInfo, error)
@ -42,6 +43,13 @@ var _ InviteService = (*inviteService)(nil)
var ErrInvalidSpaceType = fmt.Errorf("invalid space type")
type GenerateInviteParams struct {
SpaceId string
Key crypto.PrivKey
InviteType domain.InviteType
Permissions list.AclPermissions
}
type inviteService struct {
inviteStore invitestore.Service
fileAcl fileacl.Service
@ -89,60 +97,34 @@ func (i *inviteService) View(ctx context.Context, inviteCid cid.Cid, inviteFileK
}
func (i *inviteService) GetCurrent(ctx context.Context, spaceId string) (info domain.InviteInfo, err error) {
var (
fileCid, fileKey string
)
// this is for migration purposes
err = i.spaceService.TechSpace().DoSpaceView(ctx, spaceId, func(obj techspace.SpaceView) error {
fileCid, fileKey = obj.GetExistingInviteInfo()
if fileCid != "" {
info.InviteFileCid = fileCid
info.InviteFileKey = fileKey
} else {
return nil
}
_, err := obj.RemoveExistingInviteInfo()
if err != nil {
log.Warn("remove existing invite info", zap.Error(err))
}
return nil
})
if err != nil {
return domain.InviteInfo{}, getInviteError("get existing invite info from space view", err)
}
err = i.doInviteObject(ctx, spaceId, func(obj domain.InviteObject) error {
if info.InviteFileCid != "" {
return obj.SetInviteFileInfo(info.InviteFileCid, info.InviteFileKey)
}
fileCid, fileKey = obj.GetExistingInviteInfo()
info = obj.GetExistingInviteInfo()
return nil
})
if err != nil {
err = getInviteError("get existing invite info", err)
return
}
if fileCid == "" {
if info.InviteFileCid == "" {
err = ErrInviteNotExists
return
}
info.InviteFileCid = fileCid
info.InviteFileKey = fileKey
return
}
func (i *inviteService) RemoveExisting(ctx context.Context, spaceId string) (err error) {
var fileCid string
var info domain.InviteInfo
err = i.doInviteObject(ctx, spaceId, func(obj domain.InviteObject) error {
fileCid, err = obj.RemoveExistingInviteInfo()
info, err = obj.RemoveExistingInviteInfo()
return err
})
if err != nil {
return removeInviteError("remove existing invite info", err)
}
if len(fileCid) == 0 {
if len(info.InviteFileCid) == 0 {
return nil
}
invCid, err := cid.Decode(fileCid)
invCid, err := cid.Decode(info.InviteFileCid)
if err != nil {
return removeInviteError("decode invite cid", err)
}
@ -171,25 +153,22 @@ func (i *inviteService) GenerateGuestUserInvite(ctx context.Context, spaceId str
return i.generateGuestInvite(ctx, spaceId, guestUserKey)
}
func (i *inviteService) Generate(ctx context.Context, spaceId string, aclKey crypto.PrivKey, sendInvite func() error) (result domain.InviteInfo, err error) {
func (i *inviteService) Generate(ctx context.Context, params GenerateInviteParams, sendInvite func() error) (result domain.InviteInfo, err error) {
spaceId := params.SpaceId
if spaceId == i.accountService.PersonalSpaceID() {
return domain.InviteInfo{}, ErrPersonalSpace
}
var fileCid, fileKey string
err = i.doInviteObject(ctx, spaceId, func(obj domain.InviteObject) error {
fileCid, fileKey = obj.GetExistingInviteInfo()
result = obj.GetExistingInviteInfo()
return nil
})
if err != nil {
return domain.InviteInfo{}, generateInviteError("get existing invite info", err)
}
if fileCid != "" {
return domain.InviteInfo{
InviteFileCid: fileCid,
InviteFileKey: fileKey,
}, nil
if result.InviteFileCid != "" && result.InviteType == params.InviteType {
return result, nil
}
invite, err := i.buildInvite(ctx, spaceId, aclKey, nil)
invite, err := i.buildInvite(ctx, spaceId, params.Key, nil)
if err != nil {
return domain.InviteInfo{}, generateInviteError("build invite", err)
}
@ -208,8 +187,14 @@ func (i *inviteService) Generate(ctx context.Context, spaceId string, aclKey cry
removeInviteFile()
return domain.InviteInfo{}, generateInviteError("encode invite file key", err)
}
inviteInfo := domain.InviteInfo{
InviteFileCid: inviteFileCid.String(),
InviteFileKey: inviteFileKeyRaw,
InviteType: params.InviteType,
Permissions: params.Permissions,
}
err = i.doInviteObject(ctx, spaceId, func(obj domain.InviteObject) error {
return obj.SetInviteFileInfo(inviteFileCid.String(), inviteFileKeyRaw)
return obj.SetInviteFileInfo(inviteInfo)
})
if err != nil {
removeInviteFile()
@ -223,10 +208,7 @@ func (i *inviteService) Generate(ctx context.Context, spaceId string, aclKey cry
}
return domain.InviteInfo{}, generateInviteError("send invite", err)
}
return domain.InviteInfo{
InviteFileCid: inviteFileCid.String(),
InviteFileKey: inviteFileKeyRaw,
}, err
return inviteInfo, err
}
func (i *inviteService) generateGuestInvite(ctx context.Context, spaceId string, guestUserKey crypto.PrivKey) (result domain.InviteInfo, err error) {
@ -252,6 +234,11 @@ func (i *inviteService) generateGuestInvite(ctx context.Context, spaceId string,
removeInviteFile()
return domain.InviteInfo{}, generateInviteError("encode invite file key", err)
}
inviteInfo := domain.InviteInfo{
InviteFileCid: inviteFileCid.String(),
InviteFileKey: inviteFileKeyRaw,
InviteType: domain.InviteTypeGuest,
}
err = i.doInviteObject(ctx, spaceId, func(obj domain.InviteObject) error {
return obj.SetGuestInviteFileInfo(inviteFileCid.String(), inviteFileKeyRaw)
})
@ -260,10 +247,7 @@ func (i *inviteService) generateGuestInvite(ctx context.Context, spaceId string,
return domain.InviteInfo{}, generateInviteError("set invite file info", err)
}
return domain.InviteInfo{
InviteFileCid: inviteFileCid.String(),
InviteFileKey: inviteFileKeyRaw,
}, err
return inviteInfo, err
}
func (i *inviteService) GetPayload(ctx context.Context, inviteCid cid.Cid, inviteFileKey crypto.SymKey) (md *model.InvitePayload, err error) {
@ -391,6 +375,7 @@ func (i *inviteService) GetExistingGuestUserInvite(ctx context.Context, spaceId
return domain.InviteInfo{
InviteFileCid: fileCid,
InviteFileKey: fileKey,
InviteType: domain.InviteTypeGuest,
}, nil
}
return domain.InviteInfo{}, ErrInviteNotExists

View file

@ -63,7 +63,7 @@ func (mw *Middleware) SpaceMakeShareable(cctx context.Context, req *pb.RpcSpaceM
func (mw *Middleware) SpaceInviteGenerate(cctx context.Context, req *pb.RpcSpaceInviteGenerateRequest) *pb.RpcSpaceInviteGenerateResponse {
aclService := mustService[acl.AclService](mw)
inviteInfo, err := aclService.GenerateInvite(cctx, req.SpaceId)
inviteInfo, err := aclService.GenerateInvite(cctx, req.SpaceId, req.InviteType, req.Permissions)
if err != nil {
code := mapErrorCode(err,
errToCode(space.ErrSpaceDeleted, pb.RpcSpaceInviteGenerateResponseError_SPACE_IS_DELETED),

View file

@ -1641,7 +1641,6 @@
- [Rpc.Relation.ListWithValue.Response.Error.Code](#anytype-Rpc-Relation-ListWithValue-Response-Error-Code)
- [Rpc.Relation.Options.Response.Error.Code](#anytype-Rpc-Relation-Options-Response-Error-Code)
- [Rpc.Space.Delete.Response.Error.Code](#anytype-Rpc-Space-Delete-Response-Error-Code)
- [Rpc.Space.InviteGenerate.Request.InviteType](#anytype-Rpc-Space-InviteGenerate-Request-InviteType)
- [Rpc.Space.InviteGenerate.Response.Error.Code](#anytype-Rpc-Space-InviteGenerate-Response-Error-Code)
- [Rpc.Space.InviteGetCurrent.Response.Error.Code](#anytype-Rpc-Space-InviteGetCurrent-Response-Error-Code)
- [Rpc.Space.InviteGetGuest.Response.Error.Code](#anytype-Rpc-Space-InviteGetGuest-Response-Error-Code)
@ -2061,6 +2060,7 @@
- [Import.Type](#anytype-model-Import-Type)
- [InternalFlag.Value](#anytype-model-InternalFlag-Value)
- [InvitePayload.InviteType](#anytype-model-InvitePayload-InviteType)
- [InviteType](#anytype-model-InviteType)
- [LinkPreview.Type](#anytype-model-LinkPreview-Type)
- [Membership.EmailVerificationStatus](#anytype-model-Membership-EmailVerificationStatus)
- [Membership.PaymentMethod](#anytype-model-Membership-PaymentMethod)
@ -19864,7 +19864,8 @@ Available undo/redo operations
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| spaceId | [string](#string) | | |
| inviteType | [Rpc.Space.InviteGenerate.Request.InviteType](#anytype-Rpc-Space-InviteGenerate-Request-InviteType) | | |
| inviteType | [model.InviteType](#anytype-model-InviteType) | | |
| permissions | [model.ParticipantPermissions](#anytype-model-ParticipantPermissions) | | |
@ -25985,18 +25986,6 @@ Middleware-to-front-end response, that can contain a NULL error or a non-NULL er
<a name="anytype-Rpc-Space-InviteGenerate-Request-InviteType"></a>
### Rpc.Space.InviteGenerate.Request.InviteType
| Name | Number | Description |
| ---- | ------ | ----------- |
| Member | 0 | |
| Guest | 1 | |
<a name="anytype-Rpc-Space-InviteGenerate-Response-Error-Code"></a>
### Rpc.Space.InviteGenerate.Response.Error.Code
@ -32690,6 +32679,19 @@ Look https://github.com/golang/protobuf/issues/1135 for more information.
<a name="anytype-model-InviteType"></a>
### InviteType
| Name | Number | Description |
| ---- | ------ | ----------- |
| Member | 0 | aclKey contains the key to sign the ACL record |
| Guest | 1 | guestKey contains the privateKey of the guest user |
| WithoutApprove | 2 | aclKey contains the key to sign the ACL record, but no approval needed |
<a name="anytype-model-LinkPreview-Type"></a>
### LinkPreview.Type

2
go.mod
View file

@ -8,7 +8,7 @@ require (
github.com/VividCortex/ewma v1.2.0
github.com/adrium/goheif v0.0.0-20230113233934-ca402e77a786
github.com/anyproto/any-store v0.2.0
github.com/anyproto/any-sync v0.7.6-0.20250513132905-854823d81e74
github.com/anyproto/any-sync v0.7.6-0.20250513194226-2f661565dcca
github.com/anyproto/anytype-publish-server/publishclient v0.0.0-20250131145601-de288583ff2a
github.com/anyproto/anytype-push-server/pushclient v0.0.0-20250402124745-6451298047f7
github.com/anyproto/go-chash v0.1.0

2
go.sum
View file

@ -84,6 +84,8 @@ github.com/anyproto/any-sync v0.7.6-0.20250512200224-c2de5ecb12c0 h1:ZS9++jR+3NC
github.com/anyproto/any-sync v0.7.6-0.20250512200224-c2de5ecb12c0/go.mod h1:G6i3PT6pN6lcC5rim5Ed7ppUPuQgU5PyHgiqskrggL0=
github.com/anyproto/any-sync v0.7.6-0.20250513132905-854823d81e74 h1:Io1SWrvvWjnXjtJsNvRcfvv52U6H7DAHLHcpkm8Vo7A=
github.com/anyproto/any-sync v0.7.6-0.20250513132905-854823d81e74/go.mod h1:G6i3PT6pN6lcC5rim5Ed7ppUPuQgU5PyHgiqskrggL0=
github.com/anyproto/any-sync v0.7.6-0.20250513194226-2f661565dcca h1:DXXRxTfKBA8q22wCDvng182WjRjeAQqmGaZs/hXdMyo=
github.com/anyproto/any-sync v0.7.6-0.20250513194226-2f661565dcca/go.mod h1:G6i3PT6pN6lcC5rim5Ed7ppUPuQgU5PyHgiqskrggL0=
github.com/anyproto/anytype-publish-server/publishclient v0.0.0-20250131145601-de288583ff2a h1:ZZM+0OUCQMWSLSflpkf0ZMVo3V76qEDDIXPpQOClNs0=
github.com/anyproto/anytype-publish-server/publishclient v0.0.0-20250131145601-de288583ff2a/go.mod h1:4fkueCZcGniSMXkrwESO8zzERrh/L7WHimRNWecfGM0=
github.com/anyproto/anytype-push-server/pushclient v0.0.0-20250402124745-6451298047f7 h1:oKkEnxnN1jeB1Ty20CTMH3w4WkCrV8dOQy1Myetg7XA=

File diff suppressed because it is too large Load diff

View file

@ -151,11 +151,8 @@ message Rpc {
message InviteGenerate {
message Request {
string spaceId = 1;
InviteType inviteType = 2;
enum InviteType {
Member = 0;
Guest = 1;
}
model.InviteType inviteType = 2;
model.ParticipantPermissions permissions = 3;
}
message Response {

View file

@ -9,7 +9,7 @@ import (
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
)
const RelationChecksum = "62a158a458a241cdf5b502bad800a109a8917ab9026826dd1274262c46b1f839"
const RelationChecksum = "6d49bc52d95b0a12e1cb6e301e5063d505f9023d75751f28d24fa82be15bada8"
const (
RelationKeyTag domain.RelationKey = "tag"
RelationKeyCamera domain.RelationKey = "camera"
@ -126,10 +126,12 @@ const (
RelationKeySpaceAccountStatus domain.RelationKey = "spaceAccountStatus"
RelationKeySpaceInviteFileCid domain.RelationKey = "spaceInviteFileCid"
RelationKeySpaceInviteFileKey domain.RelationKey = "spaceInviteFileKey"
RelationKeySpaceInviteType domain.RelationKey = "spaceInviteType"
RelationKeySpaceInviteGuestFileCid domain.RelationKey = "spaceInviteGuestFileCid"
RelationKeySpaceInviteGuestFileKey domain.RelationKey = "spaceInviteGuestFileKey"
RelationKeyGuestKey domain.RelationKey = "guestKey"
RelationKeyParticipantPermissions domain.RelationKey = "participantPermissions"
RelationKeySpaceInvitePermissions domain.RelationKey = "spaceInvitePermissions"
RelationKeyIdentity domain.RelationKey = "identity"
RelationKeyParticipantStatus domain.RelationKey = "participantStatus"
RelationKeyIdentityProfileLink domain.RelationKey = "identityProfileLink"
@ -1880,6 +1882,34 @@ var (
ReadOnlyRelation: true,
Scope: model.Relation_type,
},
RelationKeySpaceInvitePermissions: {
DataSource: model.Relation_details,
Description: "Invite permissions. Possible values: models.ParticipantPermissions",
Format: model.RelationFormat_number,
Hidden: true,
Id: "_brspaceInvitePermissions",
Key: "spaceInvitePermissions",
MaxCount: 1,
Name: "Invite permissions",
ReadOnly: true,
ReadOnlyRelation: true,
Scope: model.Relation_type,
},
RelationKeySpaceInviteType: {
DataSource: model.Relation_details,
Description: "Encoded encryption key of invite file for current space. It stored in SpaceView",
Format: model.RelationFormat_number,
Hidden: true,
Id: "_brspaceInviteType",
Key: "spaceInviteType",
MaxCount: 1,
Name: "Invite type of space",
ReadOnly: true,
ReadOnlyRelation: true,
Scope: model.Relation_type,
},
RelationKeySpaceLocalStatus: {
DataSource: model.Relation_derived,

View file

@ -1186,6 +1186,16 @@
"readonly": true,
"source": "details"
},
{
"description": "Encoded encryption key of invite file for current space. It stored in SpaceView",
"format": "number",
"hidden": true,
"key": "spaceInviteType",
"maxCount": 1,
"name": "Invite type of space",
"readonly": true,
"source": "details"
},
{
"description": "CID of invite file for for guest user in the current space. It's stored in SpaceView",
"format": "shorttext",
@ -1226,6 +1236,16 @@
"readonly": true,
"source": "details"
},
{
"description": "Invite permissions. Possible values: models.ParticipantPermissions",
"format": "number",
"hidden": true,
"key": "spaceInvitePermissions",
"maxCount": 1,
"name": "Invite permissions",
"readonly": true,
"source": "details"
},
{
"description": "Identity",
"format": "longtext",

View file

@ -6,7 +6,7 @@ package bundle
import domain "github.com/anyproto/anytype-heart/core/domain"
const SystemRelationsChecksum = "b4130174711023e6d6e5a235a0a2e89b6d266753029ee41b9767b2c4fbb7d124"
const SystemRelationsChecksum = "af284797af811e6407b681c954b49703ced09dc457560f7717b1bece1894e97f"
// SystemRelations contains relations that have some special biz logic depends on them in some objects
// in case EVERY object depend on the relation please add it to RequiredInternalRelations
@ -70,6 +70,8 @@ var SystemRelations = append(RequiredInternalRelations, []domain.RelationKey{
RelationKeySpaceAccessType,
RelationKeySpaceInviteFileCid,
RelationKeySpaceInviteFileKey,
RelationKeySpaceInviteType,
RelationKeySpaceInvitePermissions,
RelationKeyReadersLimit,
RelationKeyWritersLimit,
RelationKeySharedSpacesLimit,

View file

@ -75,6 +75,8 @@
"spaceAccessType",
"spaceInviteFileCid",
"spaceInviteFileKey",
"spaceInviteType",
"spaceInvitePermissions",
"readersLimit",
"writersLimit",
"sharedSpacesLimit",

File diff suppressed because it is too large Load diff

View file

@ -1006,6 +1006,12 @@ enum ParticipantPermissions {
NoPermissions = 3;
}
enum InviteType {
Member = 0; // aclKey contains the key to sign the ACL record
Guest = 1; // guestKey contains the privateKey of the guest user
WithoutApprove = 2; // aclKey contains the key to sign the ACL record, but no approval needed
}
enum ParticipantStatus {
Joining = 0;
Active = 1;

View file

@ -19,7 +19,6 @@ import (
"github.com/anyproto/anytype-heart/core/block/chats/chatpush"
"github.com/anyproto/anytype-heart/space/clientspace"
"github.com/anyproto/anytype-heart/space/internal/components/aclnotifications"
"github.com/anyproto/anytype-heart/space/internal/components/invitemigrator"
"github.com/anyproto/anytype-heart/space/internal/components/participantwatcher"
"github.com/anyproto/anytype-heart/space/internal/components/spaceloader"
"github.com/anyproto/anytype-heart/space/internal/components/spacestatus"
@ -61,7 +60,6 @@ type aclObjectManager struct {
notificationService aclnotifications.AclNotification
spaceLoaderListener SpaceLoaderListener
participantWatcher participantwatcher.ParticipantWatcher
inviteMigrator invitemigrator.InviteMigrator
accountService accountservice.Service
pushNotificationService pushNotificationService
@ -114,7 +112,6 @@ func (a *aclObjectManager) Init(ap *app.App) (err error) {
if a.statService == nil {
a.statService = debugstat.NewNoOp()
}
a.inviteMigrator = app.MustComponent[invitemigrator.InviteMigrator](ap)
a.statService.AddProvider(a)
a.waitLoad = make(chan struct{})
a.wait = make(chan struct{})
@ -157,11 +154,7 @@ func (a *aclObjectManager) process() {
return
}
a.spaceLoaderListener.OnSpaceLoad(a.sp.Id())
err := a.inviteMigrator.MigrateExistingInvites(a.sp)
if err != nil {
log.Warn("migrate existing invites", zap.Error(err))
}
err = a.participantWatcher.UpdateAccountParticipantFromProfile(a.ctx, a.sp)
err := a.participantWatcher.UpdateAccountParticipantFromProfile(a.ctx, a.sp)
if err != nil {
log.Error("init my identity", zap.Error(err))
}

View file

@ -25,7 +25,6 @@ import (
"github.com/anyproto/anytype-heart/space/clientspace/mock_clientspace"
"github.com/anyproto/anytype-heart/space/internal/components/aclnotifications/mock_aclnotifications"
"github.com/anyproto/anytype-heart/space/internal/components/dependencies/mock_dependencies"
"github.com/anyproto/anytype-heart/space/internal/components/invitemigrator/mock_invitemigrator"
"github.com/anyproto/anytype-heart/space/internal/components/participantwatcher/mock_participantwatcher"
"github.com/anyproto/anytype-heart/space/internal/components/spaceloader/mock_spaceloader"
"github.com/anyproto/anytype-heart/space/internal/components/spacestatus/mock_spacestatus"
@ -70,7 +69,6 @@ func TestAclObjectManager(t *testing.T) {
fx := newFixture(t)
defer fx.finish(t)
fx.mockLoader.EXPECT().WaitLoad(mock.Anything).Return(fx.mockSpace, nil)
fx.mockInviteMigrator.EXPECT().MigrateExistingInvites(fx.mockSpace).Return(nil)
fx.mockParticipantWatcher.EXPECT().UpdateAccountParticipantFromProfile(mock.Anything, fx.mockSpace).Return(nil)
fx.mockSpace.EXPECT().CommonSpace().Return(fx.mockCommonSpace)
fx.mockSpace.EXPECT().Id().Return("spaceId")
@ -111,7 +109,6 @@ func TestAclObjectManager(t *testing.T) {
fx := newFixture(t)
defer fx.finish(t)
fx.mockLoader.EXPECT().WaitLoad(mock.Anything).Return(fx.mockSpace, nil)
fx.mockInviteMigrator.EXPECT().MigrateExistingInvites(fx.mockSpace).Return(nil)
fx.mockParticipantWatcher.EXPECT().UpdateAccountParticipantFromProfile(mock.Anything, fx.mockSpace).Return(nil)
fx.mockSpace.EXPECT().CommonSpace().Return(fx.mockCommonSpace)
fx.mockSpace.EXPECT().Id().Return("spaceId")
@ -158,7 +155,6 @@ func TestAclObjectManager(t *testing.T) {
fx := newFixture(t)
defer fx.finish(t)
fx.mockLoader.EXPECT().WaitLoad(mock.Anything).Return(fx.mockSpace, nil)
fx.mockInviteMigrator.EXPECT().MigrateExistingInvites(fx.mockSpace).Return(nil)
fx.mockStatus.EXPECT().SetOwner(a.ActualAccounts()["a"].Acl.AclState().Identity().Account(), mock.Anything).Return(nil)
fx.mockParticipantWatcher.EXPECT().UpdateAccountParticipantFromProfile(mock.Anything, fx.mockSpace).Return(nil)
fx.mockSpace.EXPECT().CommonSpace().Return(fx.mockCommonSpace)
@ -198,7 +194,6 @@ type fixture struct {
mockCommonSpace *mock_commonspace.MockSpace
mockParticipantWatcher *mock_participantwatcher.MockParticipantWatcher
mockAclNotification *mock_aclnotifications.MockAclNotification
mockInviteMigrator *mock_invitemigrator.MockInviteMigrator
mockAccountService *mock_accountservice.MockService
spaceLoaderListener *testSpaceLoaderListener
}
@ -216,12 +211,10 @@ func newFixture(t *testing.T) *fixture {
mockCommonSpace: mock_commonspace.NewMockSpace(ctrl),
mockParticipantWatcher: mock_participantwatcher.NewMockParticipantWatcher(t),
mockAclNotification: mock_aclnotifications.NewMockAclNotification(t),
mockInviteMigrator: mock_invitemigrator.NewMockInviteMigrator(t),
mockAccountService: mock_accountservice.NewMockService(ctrl),
spaceLoaderListener: &testSpaceLoaderListener{},
}
fx.a.Register(testutil.PrepareMock(ctx, fx.a, fx.mockStatus)).
Register(testutil.PrepareMock(ctx, fx.a, fx.mockInviteMigrator)).
Register(testutil.PrepareMock(ctx, fx.a, fx.mockIndexer)).
Register(testutil.PrepareMock(ctx, fx.a, fx.mockLoader)).
Register(testutil.PrepareMock(ctx, fx.a, fx.mockParticipantWatcher)).

View file

@ -1,62 +0,0 @@
package invitemigrator
import (
"fmt"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/app/logger"
"go.uber.org/zap"
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
"github.com/anyproto/anytype-heart/core/domain"
"github.com/anyproto/anytype-heart/space/clientspace"
"github.com/anyproto/anytype-heart/space/internal/components/spacestatus"
)
const CName = "client.components.invitemigrator"
var log = logger.NewNamed(CName)
func New() InviteMigrator {
return &inviteMigrator{}
}
type InviteMigrator interface {
app.Component
MigrateExistingInvites(space clientspace.Space) error
}
type inviteMigrator struct {
status spacestatus.SpaceStatus
}
func (i *inviteMigrator) Init(a *app.App) (err error) {
i.status = app.MustComponent[spacestatus.SpaceStatus](a)
return nil
}
func (i *inviteMigrator) Name() (name string) {
return CName
}
func (i *inviteMigrator) MigrateExistingInvites(space clientspace.Space) error {
spaceView := i.status.GetSpaceView()
spaceView.Lock()
fileCid, fileKey := spaceView.GetExistingInviteInfo()
if fileCid == "" {
spaceView.Unlock()
return nil
}
_, err := spaceView.RemoveExistingInviteInfo()
if err != nil {
log.Warn("remove existing invite info", zap.Error(err))
}
spaceView.Unlock()
return space.Do(space.DerivedIDs().Workspace, func(sb smartblock.SmartBlock) error {
invObject, ok := sb.(domain.InviteObject)
if !ok {
return fmt.Errorf("space is not invite object")
}
return invObject.SetInviteFileInfo(fileCid, fileKey)
})
}

View file

@ -1,80 +0,0 @@
package invitemigrator
import (
"context"
"testing"
"github.com/anyproto/any-sync/app"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
"github.com/anyproto/anytype-heart/core/block/editor/smartblock/smarttest"
"github.com/anyproto/anytype-heart/core/domain/mock_domain"
"github.com/anyproto/anytype-heart/pkg/lib/threads"
"github.com/anyproto/anytype-heart/space/clientspace/mock_clientspace"
"github.com/anyproto/anytype-heart/space/internal/components/spacestatus/mock_spacestatus"
"github.com/anyproto/anytype-heart/space/techspace/mock_techspace"
"github.com/anyproto/anytype-heart/tests/testutil"
)
type mockInviteObject struct {
smartblock.SmartBlock
*mock_domain.MockInviteObject
}
func TestInviteMigrator(t *testing.T) {
t.Run("migrate existing invites", func(t *testing.T) {
fx := newFixture(t)
fx.mockStatus.EXPECT().GetSpaceView().Return(fx.mockSpaceView)
fx.mockSpaceView.EXPECT().Lock()
fx.mockSpaceView.EXPECT().Unlock()
fx.mockSpaceView.EXPECT().GetExistingInviteInfo().Return("fileCid", "fileKey")
fx.mockSpaceView.EXPECT().RemoveExistingInviteInfo().Return("fileCid", nil)
fx.mockSpaceObject.EXPECT().DerivedIDs().Return(threads.DerivedSmartblockIds{
Workspace: "workspaceId",
})
fx.mockSpaceObject.EXPECT().Do("workspaceId", mock.Anything).RunAndReturn(func(s string, f func(smartblock.SmartBlock) error) error {
return f(mockInviteObject{SmartBlock: smarttest.New("root"), MockInviteObject: fx.mockInviteObject})
})
fx.mockInviteObject.EXPECT().SetInviteFileInfo("fileCid", "fileKey").Return(nil)
err := fx.MigrateExistingInvites(fx.mockSpaceObject)
require.NoError(t, err)
})
t.Run("migrate existing invites empty spaceview", func(t *testing.T) {
fx := newFixture(t)
fx.mockStatus.EXPECT().GetSpaceView().Return(fx.mockSpaceView)
fx.mockSpaceView.EXPECT().Lock()
fx.mockSpaceView.EXPECT().Unlock()
fx.mockSpaceView.EXPECT().GetExistingInviteInfo().Return("", "")
err := fx.MigrateExistingInvites(fx.mockSpaceObject)
require.NoError(t, err)
})
}
var ctx = context.Background()
type fixture struct {
*inviteMigrator
a *app.App
mockStatus *mock_spacestatus.MockSpaceStatus
mockSpaceView *mock_techspace.MockSpaceView
mockInviteObject *mock_domain.MockInviteObject
mockSpaceObject *mock_clientspace.MockSpace
}
func newFixture(t *testing.T) *fixture {
fx := &fixture{
inviteMigrator: New().(*inviteMigrator),
a: new(app.App),
mockStatus: mock_spacestatus.NewMockSpaceStatus(t),
mockSpaceView: mock_techspace.NewMockSpaceView(t),
mockInviteObject: mock_domain.NewMockInviteObject(t),
mockSpaceObject: mock_clientspace.NewMockSpace(t),
}
fx.a.Register(testutil.PrepareMock(ctx, fx.a, fx.mockStatus)).
Register(fx)
require.NoError(t, fx.a.Start(ctx))
return fx
}

View file

@ -1,174 +0,0 @@
// Code generated by mockery. DO NOT EDIT.
package mock_invitemigrator
import (
app "github.com/anyproto/any-sync/app"
clientspace "github.com/anyproto/anytype-heart/space/clientspace"
mock "github.com/stretchr/testify/mock"
)
// MockInviteMigrator is an autogenerated mock type for the InviteMigrator type
type MockInviteMigrator struct {
mock.Mock
}
type MockInviteMigrator_Expecter struct {
mock *mock.Mock
}
func (_m *MockInviteMigrator) EXPECT() *MockInviteMigrator_Expecter {
return &MockInviteMigrator_Expecter{mock: &_m.Mock}
}
// Init provides a mock function with given fields: a
func (_m *MockInviteMigrator) 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
}
// MockInviteMigrator_Init_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Init'
type MockInviteMigrator_Init_Call struct {
*mock.Call
}
// Init is a helper method to define mock.On call
// - a *app.App
func (_e *MockInviteMigrator_Expecter) Init(a interface{}) *MockInviteMigrator_Init_Call {
return &MockInviteMigrator_Init_Call{Call: _e.mock.On("Init", a)}
}
func (_c *MockInviteMigrator_Init_Call) Run(run func(a *app.App)) *MockInviteMigrator_Init_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(*app.App))
})
return _c
}
func (_c *MockInviteMigrator_Init_Call) Return(err error) *MockInviteMigrator_Init_Call {
_c.Call.Return(err)
return _c
}
func (_c *MockInviteMigrator_Init_Call) RunAndReturn(run func(*app.App) error) *MockInviteMigrator_Init_Call {
_c.Call.Return(run)
return _c
}
// MigrateExistingInvites provides a mock function with given fields: space
func (_m *MockInviteMigrator) MigrateExistingInvites(space clientspace.Space) error {
ret := _m.Called(space)
if len(ret) == 0 {
panic("no return value specified for MigrateExistingInvites")
}
var r0 error
if rf, ok := ret.Get(0).(func(clientspace.Space) error); ok {
r0 = rf(space)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockInviteMigrator_MigrateExistingInvites_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MigrateExistingInvites'
type MockInviteMigrator_MigrateExistingInvites_Call struct {
*mock.Call
}
// MigrateExistingInvites is a helper method to define mock.On call
// - space clientspace.Space
func (_e *MockInviteMigrator_Expecter) MigrateExistingInvites(space interface{}) *MockInviteMigrator_MigrateExistingInvites_Call {
return &MockInviteMigrator_MigrateExistingInvites_Call{Call: _e.mock.On("MigrateExistingInvites", space)}
}
func (_c *MockInviteMigrator_MigrateExistingInvites_Call) Run(run func(space clientspace.Space)) *MockInviteMigrator_MigrateExistingInvites_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(clientspace.Space))
})
return _c
}
func (_c *MockInviteMigrator_MigrateExistingInvites_Call) Return(_a0 error) *MockInviteMigrator_MigrateExistingInvites_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *MockInviteMigrator_MigrateExistingInvites_Call) RunAndReturn(run func(clientspace.Space) error) *MockInviteMigrator_MigrateExistingInvites_Call {
_c.Call.Return(run)
return _c
}
// Name provides a mock function with given fields:
func (_m *MockInviteMigrator) 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
}
// MockInviteMigrator_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name'
type MockInviteMigrator_Name_Call struct {
*mock.Call
}
// Name is a helper method to define mock.On call
func (_e *MockInviteMigrator_Expecter) Name() *MockInviteMigrator_Name_Call {
return &MockInviteMigrator_Name_Call{Call: _e.mock.On("Name")}
}
func (_c *MockInviteMigrator_Name_Call) Run(run func()) *MockInviteMigrator_Name_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockInviteMigrator_Name_Call) Return(name string) *MockInviteMigrator_Name_Call {
_c.Call.Return(name)
return _c
}
func (_c *MockInviteMigrator_Name_Call) RunAndReturn(run func() string) *MockInviteMigrator_Name_Call {
_c.Call.Return(run)
return _c
}
// NewMockInviteMigrator creates a new instance of MockInviteMigrator. 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 NewMockInviteMigrator(t interface {
mock.TestingT
Cleanup(func())
}) *MockInviteMigrator {
mock := &MockInviteMigrator{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View file

@ -10,7 +10,6 @@ import (
"github.com/anyproto/anytype-heart/space/internal/components/aclnotifications"
"github.com/anyproto/anytype-heart/space/internal/components/aclobjectmanager"
"github.com/anyproto/anytype-heart/space/internal/components/builder"
"github.com/anyproto/anytype-heart/space/internal/components/invitemigrator"
"github.com/anyproto/anytype-heart/space/internal/components/migration"
"github.com/anyproto/anytype-heart/space/internal/components/participantwatcher"
"github.com/anyproto/anytype-heart/space/internal/components/spaceloader"
@ -44,7 +43,6 @@ func New(app *app.App, params Params) Loader {
Register(spaceloader.New(params.IsPersonal, false)).
Register(aclnotifications.NewAclNotificationSender()).
Register(aclobjectmanager.New(params.OwnerMetadata, params.GuestKey)).
Register(invitemigrator.New()).
Register(participantwatcher.New()).
Register(migration.New())
for _, comp := range params.AdditionalComps {

View file

@ -75,14 +75,11 @@ type SpaceView interface {
GetLocalInfo() spaceinfo.SpaceLocalInfo
SetSpaceData(details *domain.Details) error
SetSpaceLocalInfo(info spaceinfo.SpaceLocalInfo) error
SetInviteFileInfo(fileCid string, fileKey string) (err error)
SetAccessType(acc spaceinfo.AccessType) error
SetAclIsEmpty(isEmpty bool) (err error)
SetOwner(ownerId string, createdDate int64) (err error)
SetSpacePersistentInfo(info spaceinfo.SpacePersistentInfo) error
RemoveExistingInviteInfo() (fileCid string, err error)
GetSpaceDescription() (data spaceinfo.SpaceDescription)
GetExistingInviteInfo() (fileCid string, fileKey string)
SetSharedSpacesLimit(limits int) (err error)
GetSharedSpacesLimit() (limits int)
}