mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-06 11:59:51 +08:00
feat(core): integrate realtime features (#15003)
This commit is contained in:
-191
@@ -1,191 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public struct CurrentUserProfile: AffineGraphQL.SelectionSet, Fragment {
|
||||
public static var fragmentDefinition: StaticString {
|
||||
#"fragment CurrentUserProfile on UserType { __typename id name email avatarUrl emailVerified features settings { __typename receiveInvitationEmail receiveMentionEmail receiveCommentEmail } quota { __typename name blobLimit storageQuota historyPeriod memberLimit humanReadable { __typename name blobLimit storageQuota historyPeriod memberLimit } } quotaUsage { __typename storageQuota } copilot { __typename quota { __typename limit used } } }"#
|
||||
}
|
||||
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("id", AffineGraphQL.ID.self),
|
||||
.field("name", String.self),
|
||||
.field("email", String.self),
|
||||
.field("avatarUrl", String?.self),
|
||||
.field("emailVerified", Bool.self),
|
||||
.field("features", [GraphQLEnum<AffineGraphQL.FeatureType>].self),
|
||||
.field("settings", Settings.self),
|
||||
.field("quota", Quota.self),
|
||||
.field("quotaUsage", QuotaUsage.self),
|
||||
.field("copilot", Copilot.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
CurrentUserProfile.self
|
||||
] }
|
||||
|
||||
public var id: AffineGraphQL.ID { __data["id"] }
|
||||
/// User name
|
||||
public var name: String { __data["name"] }
|
||||
/// User email
|
||||
public var email: String { __data["email"] }
|
||||
/// User avatar url
|
||||
public var avatarUrl: String? { __data["avatarUrl"] }
|
||||
/// User email verified
|
||||
public var emailVerified: Bool { __data["emailVerified"] }
|
||||
/// Enabled features of a user
|
||||
public var features: [GraphQLEnum<AffineGraphQL.FeatureType>] { __data["features"] }
|
||||
/// Get user settings
|
||||
public var settings: Settings { __data["settings"] }
|
||||
public var quota: Quota { __data["quota"] }
|
||||
public var quotaUsage: QuotaUsage { __data["quotaUsage"] }
|
||||
public var copilot: Copilot { __data["copilot"] }
|
||||
|
||||
/// Settings
|
||||
///
|
||||
/// Parent Type: `UserSettingsType`
|
||||
public struct Settings: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserSettingsType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("receiveInvitationEmail", Bool.self),
|
||||
.field("receiveMentionEmail", Bool.self),
|
||||
.field("receiveCommentEmail", Bool.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
CurrentUserProfile.Settings.self
|
||||
] }
|
||||
|
||||
/// Receive invitation email
|
||||
public var receiveInvitationEmail: Bool { __data["receiveInvitationEmail"] }
|
||||
/// Receive mention email
|
||||
public var receiveMentionEmail: Bool { __data["receiveMentionEmail"] }
|
||||
/// Receive comment email
|
||||
public var receiveCommentEmail: Bool { __data["receiveCommentEmail"] }
|
||||
}
|
||||
|
||||
/// Quota
|
||||
///
|
||||
/// Parent Type: `UserQuotaType`
|
||||
public struct Quota: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserQuotaType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("name", String.self),
|
||||
.field("blobLimit", AffineGraphQL.SafeInt.self),
|
||||
.field("storageQuota", AffineGraphQL.SafeInt.self),
|
||||
.field("historyPeriod", AffineGraphQL.SafeInt.self),
|
||||
.field("memberLimit", Int.self),
|
||||
.field("humanReadable", HumanReadable.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
CurrentUserProfile.Quota.self
|
||||
] }
|
||||
|
||||
public var name: String { __data["name"] }
|
||||
public var blobLimit: AffineGraphQL.SafeInt { __data["blobLimit"] }
|
||||
public var storageQuota: AffineGraphQL.SafeInt { __data["storageQuota"] }
|
||||
public var historyPeriod: AffineGraphQL.SafeInt { __data["historyPeriod"] }
|
||||
public var memberLimit: Int { __data["memberLimit"] }
|
||||
public var humanReadable: HumanReadable { __data["humanReadable"] }
|
||||
|
||||
/// Quota.HumanReadable
|
||||
///
|
||||
/// Parent Type: `UserQuotaHumanReadableType`
|
||||
public struct HumanReadable: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserQuotaHumanReadableType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("name", String.self),
|
||||
.field("blobLimit", String.self),
|
||||
.field("storageQuota", String.self),
|
||||
.field("historyPeriod", String.self),
|
||||
.field("memberLimit", String.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
CurrentUserProfile.Quota.HumanReadable.self
|
||||
] }
|
||||
|
||||
public var name: String { __data["name"] }
|
||||
public var blobLimit: String { __data["blobLimit"] }
|
||||
public var storageQuota: String { __data["storageQuota"] }
|
||||
public var historyPeriod: String { __data["historyPeriod"] }
|
||||
public var memberLimit: String { __data["memberLimit"] }
|
||||
}
|
||||
}
|
||||
|
||||
/// QuotaUsage
|
||||
///
|
||||
/// Parent Type: `UserQuotaUsageType`
|
||||
public struct QuotaUsage: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserQuotaUsageType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("storageQuota", AffineGraphQL.SafeInt.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
CurrentUserProfile.QuotaUsage.self
|
||||
] }
|
||||
|
||||
@available(*, deprecated, message: "use `UserQuotaType[\'usedStorageQuota\']` instead")
|
||||
public var storageQuota: AffineGraphQL.SafeInt { __data["storageQuota"] }
|
||||
}
|
||||
|
||||
/// Copilot
|
||||
///
|
||||
/// Parent Type: `Copilot`
|
||||
public struct Copilot: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Copilot }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("quota", Quota.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
CurrentUserProfile.Copilot.self
|
||||
] }
|
||||
|
||||
/// Get the quota of the user in the workspace
|
||||
public var quota: Quota { __data["quota"] }
|
||||
|
||||
/// Copilot.Quota
|
||||
///
|
||||
/// Parent Type: `CopilotQuota`
|
||||
public struct Quota: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.CopilotQuota }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("limit", AffineGraphQL.SafeInt?.self),
|
||||
.field("used", AffineGraphQL.SafeInt.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
CurrentUserProfile.Copilot.Quota.self
|
||||
] }
|
||||
|
||||
public var limit: AffineGraphQL.SafeInt? { __data["limit"] }
|
||||
public var used: AffineGraphQL.SafeInt { __data["used"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class GetCurrentUserProfileQuery: GraphQLQuery {
|
||||
public static let operationName: String = "getCurrentUserProfile"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"query getCurrentUserProfile { currentUser { __typename ...CurrentUserProfile } }"#,
|
||||
fragments: [CurrentUserProfile.self]
|
||||
))
|
||||
|
||||
public init() {}
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("currentUser", CurrentUser?.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetCurrentUserProfileQuery.Data.self
|
||||
] }
|
||||
|
||||
/// Get current user
|
||||
public var currentUser: CurrentUser? { __data["currentUser"] }
|
||||
|
||||
/// CurrentUser
|
||||
///
|
||||
/// Parent Type: `UserType`
|
||||
public struct CurrentUser: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.fragment(CurrentUserProfile.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetCurrentUserProfileQuery.Data.CurrentUser.self,
|
||||
CurrentUserProfile.self
|
||||
] }
|
||||
|
||||
public var id: AffineGraphQL.ID { __data["id"] }
|
||||
/// User name
|
||||
public var name: String { __data["name"] }
|
||||
/// User email
|
||||
public var email: String { __data["email"] }
|
||||
/// User avatar url
|
||||
public var avatarUrl: String? { __data["avatarUrl"] }
|
||||
/// User email verified
|
||||
public var emailVerified: Bool { __data["emailVerified"] }
|
||||
/// Enabled features of a user
|
||||
public var features: [GraphQLEnum<AffineGraphQL.FeatureType>] { __data["features"] }
|
||||
/// Get user settings
|
||||
public var settings: Settings { __data["settings"] }
|
||||
public var quota: Quota { __data["quota"] }
|
||||
public var quotaUsage: QuotaUsage { __data["quotaUsage"] }
|
||||
public var copilot: Copilot { __data["copilot"] }
|
||||
|
||||
public struct Fragments: FragmentContainer {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public var currentUserProfile: CurrentUserProfile { _toFragment() }
|
||||
}
|
||||
|
||||
public typealias Settings = CurrentUserProfile.Settings
|
||||
|
||||
public typealias Quota = CurrentUserProfile.Quota
|
||||
|
||||
public typealias QuotaUsage = CurrentUserProfile.QuotaUsage
|
||||
|
||||
public typealias Copilot = CurrentUserProfile.Copilot
|
||||
}
|
||||
}
|
||||
}
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class GetDocDefaultRoleQuery: GraphQLQuery {
|
||||
public static let operationName: String = "getDocDefaultRole"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"query getDocDefaultRole($workspaceId: String!, $docId: String!) { workspace(id: $workspaceId) { __typename doc(docId: $docId) { __typename defaultRole } } }"#
|
||||
))
|
||||
|
||||
public var workspaceId: String
|
||||
public var docId: String
|
||||
|
||||
public init(
|
||||
workspaceId: String,
|
||||
docId: String
|
||||
) {
|
||||
self.workspaceId = workspaceId
|
||||
self.docId = docId
|
||||
}
|
||||
|
||||
public var __variables: Variables? { [
|
||||
"workspaceId": workspaceId,
|
||||
"docId": docId
|
||||
] }
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("workspace", Workspace.self, arguments: ["id": .variable("workspaceId")]),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetDocDefaultRoleQuery.Data.self
|
||||
] }
|
||||
|
||||
/// Get workspace by id
|
||||
public var workspace: Workspace { __data["workspace"] }
|
||||
|
||||
/// Workspace
|
||||
///
|
||||
/// Parent Type: `WorkspaceType`
|
||||
public struct Workspace: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("doc", Doc.self, arguments: ["docId": .variable("docId")]),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetDocDefaultRoleQuery.Data.Workspace.self
|
||||
] }
|
||||
|
||||
/// Get get with given id
|
||||
public var doc: Doc { __data["doc"] }
|
||||
|
||||
/// Workspace.Doc
|
||||
///
|
||||
/// Parent Type: `DocType`
|
||||
public struct Doc: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.DocType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("defaultRole", GraphQLEnum<AffineGraphQL.DocRole>.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetDocDefaultRoleQuery.Data.Workspace.Doc.self
|
||||
] }
|
||||
|
||||
public var defaultRole: GraphQLEnum<AffineGraphQL.DocRole> { __data["defaultRole"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-120
@@ -1,120 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class GetMembersByWorkspaceIdQuery: GraphQLQuery {
|
||||
public static let operationName: String = "getMembersByWorkspaceId"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"query getMembersByWorkspaceId($workspaceId: String!, $skip: Int, $take: Int, $query: String) { workspace(id: $workspaceId) { __typename memberCount members(skip: $skip, take: $take, query: $query) { __typename id name email avatarUrl permission inviteId emailVerified status } } }"#
|
||||
))
|
||||
|
||||
public var workspaceId: String
|
||||
public var skip: GraphQLNullable<Int>
|
||||
public var take: GraphQLNullable<Int>
|
||||
public var query: GraphQLNullable<String>
|
||||
|
||||
public init(
|
||||
workspaceId: String,
|
||||
skip: GraphQLNullable<Int>,
|
||||
take: GraphQLNullable<Int>,
|
||||
query: GraphQLNullable<String>
|
||||
) {
|
||||
self.workspaceId = workspaceId
|
||||
self.skip = skip
|
||||
self.take = take
|
||||
self.query = query
|
||||
}
|
||||
|
||||
public var __variables: Variables? { [
|
||||
"workspaceId": workspaceId,
|
||||
"skip": skip,
|
||||
"take": take,
|
||||
"query": query
|
||||
] }
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("workspace", Workspace.self, arguments: ["id": .variable("workspaceId")]),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetMembersByWorkspaceIdQuery.Data.self
|
||||
] }
|
||||
|
||||
/// Get workspace by id
|
||||
public var workspace: Workspace { __data["workspace"] }
|
||||
|
||||
/// Workspace
|
||||
///
|
||||
/// Parent Type: `WorkspaceType`
|
||||
public struct Workspace: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("memberCount", Int.self),
|
||||
.field("members", [Member].self, arguments: [
|
||||
"skip": .variable("skip"),
|
||||
"take": .variable("take"),
|
||||
"query": .variable("query")
|
||||
]),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetMembersByWorkspaceIdQuery.Data.Workspace.self
|
||||
] }
|
||||
|
||||
/// member count of workspace
|
||||
public var memberCount: Int { __data["memberCount"] }
|
||||
/// Members of workspace
|
||||
public var members: [Member] { __data["members"] }
|
||||
|
||||
/// Workspace.Member
|
||||
///
|
||||
/// Parent Type: `InviteUserType`
|
||||
public struct Member: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.InviteUserType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("id", AffineGraphQL.ID.self),
|
||||
.field("name", String?.self),
|
||||
.field("email", String?.self),
|
||||
.field("avatarUrl", String?.self),
|
||||
.field("permission", GraphQLEnum<AffineGraphQL.Permission>.self),
|
||||
.field("inviteId", String.self),
|
||||
.field("emailVerified", Bool?.self),
|
||||
.field("status", GraphQLEnum<AffineGraphQL.WorkspaceMemberStatus>.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetMembersByWorkspaceIdQuery.Data.Workspace.Member.self
|
||||
] }
|
||||
|
||||
public var id: AffineGraphQL.ID { __data["id"] }
|
||||
/// User name
|
||||
public var name: String? { __data["name"] }
|
||||
/// User email
|
||||
public var email: String? { __data["email"] }
|
||||
/// User avatar url
|
||||
public var avatarUrl: String? { __data["avatarUrl"] }
|
||||
/// User permission in workspace
|
||||
@available(*, deprecated, message: "Use role instead")
|
||||
public var permission: GraphQLEnum<AffineGraphQL.Permission> { __data["permission"] }
|
||||
/// Invite id
|
||||
public var inviteId: String { __data["inviteId"] }
|
||||
/// User email verified
|
||||
public var emailVerified: Bool? { __data["emailVerified"] }
|
||||
/// Member invite status in workspace
|
||||
public var status: GraphQLEnum<AffineGraphQL.WorkspaceMemberStatus> { __data["status"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-197
@@ -1,197 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class GetPageGrantedUsersListQuery: GraphQLQuery {
|
||||
public static let operationName: String = "getPageGrantedUsersList"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"query getPageGrantedUsersList($pagination: PaginationInput!, $docId: String!, $workspaceId: String!) { workspace(id: $workspaceId) { __typename doc(docId: $docId) { __typename grantedUsersList(pagination: $pagination) { __typename totalCount pageInfo { __typename endCursor hasNextPage } edges { __typename node { __typename role user { __typename id name email avatarUrl } } } } } } }"#
|
||||
))
|
||||
|
||||
public var pagination: PaginationInput
|
||||
public var docId: String
|
||||
public var workspaceId: String
|
||||
|
||||
public init(
|
||||
pagination: PaginationInput,
|
||||
docId: String,
|
||||
workspaceId: String
|
||||
) {
|
||||
self.pagination = pagination
|
||||
self.docId = docId
|
||||
self.workspaceId = workspaceId
|
||||
}
|
||||
|
||||
public var __variables: Variables? { [
|
||||
"pagination": pagination,
|
||||
"docId": docId,
|
||||
"workspaceId": workspaceId
|
||||
] }
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("workspace", Workspace.self, arguments: ["id": .variable("workspaceId")]),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetPageGrantedUsersListQuery.Data.self
|
||||
] }
|
||||
|
||||
/// Get workspace by id
|
||||
public var workspace: Workspace { __data["workspace"] }
|
||||
|
||||
/// Workspace
|
||||
///
|
||||
/// Parent Type: `WorkspaceType`
|
||||
public struct Workspace: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("doc", Doc.self, arguments: ["docId": .variable("docId")]),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetPageGrantedUsersListQuery.Data.Workspace.self
|
||||
] }
|
||||
|
||||
/// Get get with given id
|
||||
public var doc: Doc { __data["doc"] }
|
||||
|
||||
/// Workspace.Doc
|
||||
///
|
||||
/// Parent Type: `DocType`
|
||||
public struct Doc: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.DocType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("grantedUsersList", GrantedUsersList.self, arguments: ["pagination": .variable("pagination")]),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetPageGrantedUsersListQuery.Data.Workspace.Doc.self
|
||||
] }
|
||||
|
||||
/// paginated doc granted users list
|
||||
public var grantedUsersList: GrantedUsersList { __data["grantedUsersList"] }
|
||||
|
||||
/// Workspace.Doc.GrantedUsersList
|
||||
///
|
||||
/// Parent Type: `PaginatedGrantedDocUserType`
|
||||
public struct GrantedUsersList: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PaginatedGrantedDocUserType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("totalCount", Int.self),
|
||||
.field("pageInfo", PageInfo.self),
|
||||
.field("edges", [Edge].self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetPageGrantedUsersListQuery.Data.Workspace.Doc.GrantedUsersList.self
|
||||
] }
|
||||
|
||||
public var totalCount: Int { __data["totalCount"] }
|
||||
public var pageInfo: PageInfo { __data["pageInfo"] }
|
||||
public var edges: [Edge] { __data["edges"] }
|
||||
|
||||
/// Workspace.Doc.GrantedUsersList.PageInfo
|
||||
///
|
||||
/// Parent Type: `PageInfo`
|
||||
public struct PageInfo: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PageInfo }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("endCursor", String?.self),
|
||||
.field("hasNextPage", Bool.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetPageGrantedUsersListQuery.Data.Workspace.Doc.GrantedUsersList.PageInfo.self
|
||||
] }
|
||||
|
||||
public var endCursor: String? { __data["endCursor"] }
|
||||
public var hasNextPage: Bool { __data["hasNextPage"] }
|
||||
}
|
||||
|
||||
/// Workspace.Doc.GrantedUsersList.Edge
|
||||
///
|
||||
/// Parent Type: `GrantedDocUserTypeEdge`
|
||||
public struct Edge: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.GrantedDocUserTypeEdge }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("node", Node.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetPageGrantedUsersListQuery.Data.Workspace.Doc.GrantedUsersList.Edge.self
|
||||
] }
|
||||
|
||||
public var node: Node { __data["node"] }
|
||||
|
||||
/// Workspace.Doc.GrantedUsersList.Edge.Node
|
||||
///
|
||||
/// Parent Type: `GrantedDocUserType`
|
||||
public struct Node: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.GrantedDocUserType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("role", GraphQLEnum<AffineGraphQL.DocRole>.self),
|
||||
.field("user", User.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetPageGrantedUsersListQuery.Data.Workspace.Doc.GrantedUsersList.Edge.Node.self
|
||||
] }
|
||||
|
||||
public var role: GraphQLEnum<AffineGraphQL.DocRole> { __data["role"] }
|
||||
public var user: User { __data["user"] }
|
||||
|
||||
/// Workspace.Doc.GrantedUsersList.Edge.Node.User
|
||||
///
|
||||
/// Parent Type: `WorkspaceUserType`
|
||||
public struct User: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceUserType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("id", String.self),
|
||||
.field("name", String.self),
|
||||
.field("email", String.self),
|
||||
.field("avatarUrl", String?.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetPageGrantedUsersListQuery.Data.Workspace.Doc.GrantedUsersList.Edge.Node.User.self
|
||||
] }
|
||||
|
||||
public var id: String { __data["id"] }
|
||||
public var name: String { __data["name"] }
|
||||
public var email: String { __data["email"] }
|
||||
public var avatarUrl: String? { __data["avatarUrl"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class GetWorkspaceConfigQuery: GraphQLQuery {
|
||||
public static let operationName: String = "getWorkspaceConfig"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"query getWorkspaceConfig($id: String!) { workspace(id: $id) { __typename enableAi enableSharing enableUrlPreview enableDocEmbedding } }"#
|
||||
))
|
||||
|
||||
public var id: String
|
||||
|
||||
public init(id: String) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
public var __variables: Variables? { ["id": id] }
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("workspace", Workspace.self, arguments: ["id": .variable("id")]),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetWorkspaceConfigQuery.Data.self
|
||||
] }
|
||||
|
||||
/// Get workspace by id
|
||||
public var workspace: Workspace { __data["workspace"] }
|
||||
|
||||
/// Workspace
|
||||
///
|
||||
/// Parent Type: `WorkspaceType`
|
||||
public struct Workspace: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("enableAi", Bool.self),
|
||||
.field("enableSharing", Bool.self),
|
||||
.field("enableUrlPreview", Bool.self),
|
||||
.field("enableDocEmbedding", Bool.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetWorkspaceConfigQuery.Data.Workspace.self
|
||||
] }
|
||||
|
||||
/// Enable AI
|
||||
public var enableAi: Bool { __data["enableAi"] }
|
||||
/// Enable workspace sharing
|
||||
public var enableSharing: Bool { __data["enableSharing"] }
|
||||
/// Enable url previous when sharing
|
||||
public var enableUrlPreview: Bool { __data["enableUrlPreview"] }
|
||||
/// Enable doc embedding
|
||||
public var enableDocEmbedding: Bool { __data["enableDocEmbedding"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class GetWorkspaceEmbeddingStatusQuery: GraphQLQuery {
|
||||
public static let operationName: String = "getWorkspaceEmbeddingStatus"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"query getWorkspaceEmbeddingStatus($workspaceId: String!) { queryWorkspaceEmbeddingStatus(workspaceId: $workspaceId) { __typename total embedded } }"#
|
||||
))
|
||||
|
||||
public var workspaceId: String
|
||||
|
||||
public init(workspaceId: String) {
|
||||
self.workspaceId = workspaceId
|
||||
}
|
||||
|
||||
public var __variables: Variables? { ["workspaceId": workspaceId] }
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("queryWorkspaceEmbeddingStatus", QueryWorkspaceEmbeddingStatus.self, arguments: ["workspaceId": .variable("workspaceId")]),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetWorkspaceEmbeddingStatusQuery.Data.self
|
||||
] }
|
||||
|
||||
/// query workspace embedding status
|
||||
public var queryWorkspaceEmbeddingStatus: QueryWorkspaceEmbeddingStatus { __data["queryWorkspaceEmbeddingStatus"] }
|
||||
|
||||
/// QueryWorkspaceEmbeddingStatus
|
||||
///
|
||||
/// Parent Type: `ContextWorkspaceEmbeddingStatus`
|
||||
public struct QueryWorkspaceEmbeddingStatus: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.ContextWorkspaceEmbeddingStatus }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("total", AffineGraphQL.SafeInt.self),
|
||||
.field("embedded", AffineGraphQL.SafeInt.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetWorkspaceEmbeddingStatusQuery.Data.QueryWorkspaceEmbeddingStatus.self
|
||||
] }
|
||||
|
||||
public var total: AffineGraphQL.SafeInt { __data["total"] }
|
||||
public var embedded: AffineGraphQL.SafeInt { __data["embedded"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class GetWorkspaceInfoQuery: GraphQLQuery {
|
||||
public static let operationName: String = "getWorkspaceInfo"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"query getWorkspaceInfo($workspaceId: String!) { workspace(id: $workspaceId) { __typename permissions { __typename Workspace_Administrators_Manage Workspace_Blobs_List Workspace_Blobs_Read Workspace_Blobs_Write Workspace_Copilot Workspace_CreateDoc Workspace_Delete Workspace_Organize_Read Workspace_Payment_Manage Workspace_Properties_Create Workspace_Properties_Delete Workspace_Properties_Read Workspace_Properties_Update Workspace_Read Workspace_Settings_Read Workspace_Settings_Update Workspace_Sync Workspace_TransferOwner Workspace_Users_Manage Workspace_Users_Read } role team } }"#
|
||||
))
|
||||
|
||||
public var workspaceId: String
|
||||
|
||||
public init(workspaceId: String) {
|
||||
self.workspaceId = workspaceId
|
||||
}
|
||||
|
||||
public var __variables: Variables? { ["workspaceId": workspaceId] }
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("workspace", Workspace.self, arguments: ["id": .variable("workspaceId")]),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetWorkspaceInfoQuery.Data.self
|
||||
] }
|
||||
|
||||
/// Get workspace by id
|
||||
public var workspace: Workspace { __data["workspace"] }
|
||||
|
||||
/// Workspace
|
||||
///
|
||||
/// Parent Type: `WorkspaceType`
|
||||
public struct Workspace: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("permissions", Permissions.self),
|
||||
.field("role", GraphQLEnum<AffineGraphQL.Permission>.self),
|
||||
.field("team", Bool.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetWorkspaceInfoQuery.Data.Workspace.self
|
||||
] }
|
||||
|
||||
/// map of action permissions
|
||||
public var permissions: Permissions { __data["permissions"] }
|
||||
/// Role of current signed in user in workspace
|
||||
public var role: GraphQLEnum<AffineGraphQL.Permission> { __data["role"] }
|
||||
/// if workspace is team workspace
|
||||
public var team: Bool { __data["team"] }
|
||||
|
||||
/// Workspace.Permissions
|
||||
///
|
||||
/// Parent Type: `WorkspacePermissions`
|
||||
public struct Permissions: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspacePermissions }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("Workspace_Administrators_Manage", Bool.self),
|
||||
.field("Workspace_Blobs_List", Bool.self),
|
||||
.field("Workspace_Blobs_Read", Bool.self),
|
||||
.field("Workspace_Blobs_Write", Bool.self),
|
||||
.field("Workspace_Copilot", Bool.self),
|
||||
.field("Workspace_CreateDoc", Bool.self),
|
||||
.field("Workspace_Delete", Bool.self),
|
||||
.field("Workspace_Organize_Read", Bool.self),
|
||||
.field("Workspace_Payment_Manage", Bool.self),
|
||||
.field("Workspace_Properties_Create", Bool.self),
|
||||
.field("Workspace_Properties_Delete", Bool.self),
|
||||
.field("Workspace_Properties_Read", Bool.self),
|
||||
.field("Workspace_Properties_Update", Bool.self),
|
||||
.field("Workspace_Read", Bool.self),
|
||||
.field("Workspace_Settings_Read", Bool.self),
|
||||
.field("Workspace_Settings_Update", Bool.self),
|
||||
.field("Workspace_Sync", Bool.self),
|
||||
.field("Workspace_TransferOwner", Bool.self),
|
||||
.field("Workspace_Users_Manage", Bool.self),
|
||||
.field("Workspace_Users_Read", Bool.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
GetWorkspaceInfoQuery.Data.Workspace.Permissions.self
|
||||
] }
|
||||
|
||||
public var workspace_Administrators_Manage: Bool { __data["Workspace_Administrators_Manage"] }
|
||||
public var workspace_Blobs_List: Bool { __data["Workspace_Blobs_List"] }
|
||||
public var workspace_Blobs_Read: Bool { __data["Workspace_Blobs_Read"] }
|
||||
public var workspace_Blobs_Write: Bool { __data["Workspace_Blobs_Write"] }
|
||||
public var workspace_Copilot: Bool { __data["Workspace_Copilot"] }
|
||||
public var workspace_CreateDoc: Bool { __data["Workspace_CreateDoc"] }
|
||||
public var workspace_Delete: Bool { __data["Workspace_Delete"] }
|
||||
public var workspace_Organize_Read: Bool { __data["Workspace_Organize_Read"] }
|
||||
public var workspace_Payment_Manage: Bool { __data["Workspace_Payment_Manage"] }
|
||||
public var workspace_Properties_Create: Bool { __data["Workspace_Properties_Create"] }
|
||||
public var workspace_Properties_Delete: Bool { __data["Workspace_Properties_Delete"] }
|
||||
public var workspace_Properties_Read: Bool { __data["Workspace_Properties_Read"] }
|
||||
public var workspace_Properties_Update: Bool { __data["Workspace_Properties_Update"] }
|
||||
public var workspace_Read: Bool { __data["Workspace_Read"] }
|
||||
public var workspace_Settings_Read: Bool { __data["Workspace_Settings_Read"] }
|
||||
public var workspace_Settings_Update: Bool { __data["Workspace_Settings_Update"] }
|
||||
public var workspace_Sync: Bool { __data["Workspace_Sync"] }
|
||||
public var workspace_TransferOwner: Bool { __data["Workspace_TransferOwner"] }
|
||||
public var workspace_Users_Manage: Bool { __data["Workspace_Users_Manage"] }
|
||||
public var workspace_Users_Read: Bool { __data["Workspace_Users_Read"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class ListUserAccessTokensQuery: GraphQLQuery {
|
||||
public static let operationName: String = "listUserAccessTokens"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"query listUserAccessTokens { currentUser { __typename revealedAccessTokens { __typename id name createdAt expiresAt token } } }"#
|
||||
))
|
||||
|
||||
public init() {}
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("currentUser", CurrentUser?.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
ListUserAccessTokensQuery.Data.self
|
||||
] }
|
||||
|
||||
/// Get current user
|
||||
public var currentUser: CurrentUser? { __data["currentUser"] }
|
||||
|
||||
/// CurrentUser
|
||||
///
|
||||
/// Parent Type: `UserType`
|
||||
public struct CurrentUser: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("revealedAccessTokens", [RevealedAccessToken].self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
ListUserAccessTokensQuery.Data.CurrentUser.self
|
||||
] }
|
||||
|
||||
public var revealedAccessTokens: [RevealedAccessToken] { __data["revealedAccessTokens"] }
|
||||
|
||||
/// CurrentUser.RevealedAccessToken
|
||||
///
|
||||
/// Parent Type: `RevealedAccessToken`
|
||||
public struct RevealedAccessToken: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.RevealedAccessToken }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("id", String.self),
|
||||
.field("name", String.self),
|
||||
.field("createdAt", AffineGraphQL.DateTime.self),
|
||||
.field("expiresAt", AffineGraphQL.DateTime?.self),
|
||||
.field("token", String.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
ListUserAccessTokensQuery.Data.CurrentUser.RevealedAccessToken.self
|
||||
] }
|
||||
|
||||
public var id: String { __data["id"] }
|
||||
public var name: String { __data["name"] }
|
||||
public var createdAt: AffineGraphQL.DateTime { __data["createdAt"] }
|
||||
public var expiresAt: AffineGraphQL.DateTime? { __data["expiresAt"] }
|
||||
public var token: String { __data["token"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class NotificationCountQuery: GraphQLQuery {
|
||||
public static let operationName: String = "notificationCount"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"query notificationCount { currentUser { __typename notifications(pagination: { first: 1 }) { __typename totalCount } } }"#
|
||||
))
|
||||
|
||||
public init() {}
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("currentUser", CurrentUser?.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
NotificationCountQuery.Data.self
|
||||
] }
|
||||
|
||||
/// Get current user
|
||||
public var currentUser: CurrentUser? { __data["currentUser"] }
|
||||
|
||||
/// CurrentUser
|
||||
///
|
||||
/// Parent Type: `UserType`
|
||||
public struct CurrentUser: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.UserType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("notifications", Notifications.self, arguments: ["pagination": ["first": 1]]),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
NotificationCountQuery.Data.CurrentUser.self
|
||||
] }
|
||||
|
||||
/// Get current user notifications
|
||||
public var notifications: Notifications { __data["notifications"] }
|
||||
|
||||
/// CurrentUser.Notifications
|
||||
///
|
||||
/// Parent Type: `PaginatedNotificationObjectType`
|
||||
public struct Notifications: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.PaginatedNotificationObjectType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("totalCount", Int.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
NotificationCountQuery.Data.CurrentUser.Notifications.self
|
||||
] }
|
||||
|
||||
public var totalCount: Int { __data["totalCount"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
@_exported import ApolloAPI
|
||||
|
||||
public class WorkspaceQuotaQuery: GraphQLQuery {
|
||||
public static let operationName: String = "workspaceQuota"
|
||||
public static let operationDocument: ApolloAPI.OperationDocument = .init(
|
||||
definition: .init(
|
||||
#"query workspaceQuota($id: String!) { workspace(id: $id) { __typename quota { __typename name blobLimit storageQuota usedStorageQuota historyPeriod memberLimit memberCount overcapacityMemberCount humanReadable { __typename name blobLimit storageQuota historyPeriod memberLimit memberCount overcapacityMemberCount } } } }"#
|
||||
))
|
||||
|
||||
public var id: String
|
||||
|
||||
public init(id: String) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
public var __variables: Variables? { ["id": id] }
|
||||
|
||||
public struct Data: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.Query }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("workspace", Workspace.self, arguments: ["id": .variable("id")]),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
WorkspaceQuotaQuery.Data.self
|
||||
] }
|
||||
|
||||
/// Get workspace by id
|
||||
public var workspace: Workspace { __data["workspace"] }
|
||||
|
||||
/// Workspace
|
||||
///
|
||||
/// Parent Type: `WorkspaceType`
|
||||
public struct Workspace: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("quota", Quota.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
WorkspaceQuotaQuery.Data.Workspace.self
|
||||
] }
|
||||
|
||||
/// quota of workspace
|
||||
public var quota: Quota { __data["quota"] }
|
||||
|
||||
/// Workspace.Quota
|
||||
///
|
||||
/// Parent Type: `WorkspaceQuotaType`
|
||||
public struct Quota: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceQuotaType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("name", String.self),
|
||||
.field("blobLimit", AffineGraphQL.SafeInt.self),
|
||||
.field("storageQuota", AffineGraphQL.SafeInt.self),
|
||||
.field("usedStorageQuota", AffineGraphQL.SafeInt.self),
|
||||
.field("historyPeriod", AffineGraphQL.SafeInt.self),
|
||||
.field("memberLimit", Int.self),
|
||||
.field("memberCount", Int.self),
|
||||
.field("overcapacityMemberCount", Int.self),
|
||||
.field("humanReadable", HumanReadable.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
WorkspaceQuotaQuery.Data.Workspace.Quota.self
|
||||
] }
|
||||
|
||||
public var name: String { __data["name"] }
|
||||
public var blobLimit: AffineGraphQL.SafeInt { __data["blobLimit"] }
|
||||
public var storageQuota: AffineGraphQL.SafeInt { __data["storageQuota"] }
|
||||
public var usedStorageQuota: AffineGraphQL.SafeInt { __data["usedStorageQuota"] }
|
||||
public var historyPeriod: AffineGraphQL.SafeInt { __data["historyPeriod"] }
|
||||
public var memberLimit: Int { __data["memberLimit"] }
|
||||
public var memberCount: Int { __data["memberCount"] }
|
||||
public var overcapacityMemberCount: Int { __data["overcapacityMemberCount"] }
|
||||
public var humanReadable: HumanReadable { __data["humanReadable"] }
|
||||
|
||||
/// Workspace.Quota.HumanReadable
|
||||
///
|
||||
/// Parent Type: `WorkspaceQuotaHumanReadableType`
|
||||
public struct HumanReadable: AffineGraphQL.SelectionSet {
|
||||
public let __data: DataDict
|
||||
public init(_dataDict: DataDict) { __data = _dataDict }
|
||||
|
||||
public static var __parentType: any ApolloAPI.ParentType { AffineGraphQL.Objects.WorkspaceQuotaHumanReadableType }
|
||||
public static var __selections: [ApolloAPI.Selection] { [
|
||||
.field("__typename", String.self),
|
||||
.field("name", String.self),
|
||||
.field("blobLimit", String.self),
|
||||
.field("storageQuota", String.self),
|
||||
.field("historyPeriod", String.self),
|
||||
.field("memberLimit", String.self),
|
||||
.field("memberCount", String.self),
|
||||
.field("overcapacityMemberCount", String.self),
|
||||
] }
|
||||
public static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [
|
||||
WorkspaceQuotaQuery.Data.Workspace.Quota.HumanReadable.self
|
||||
] }
|
||||
|
||||
public var name: String { __data["name"] }
|
||||
public var blobLimit: String { __data["blobLimit"] }
|
||||
public var storageQuota: String { __data["storageQuota"] }
|
||||
public var historyPeriod: String { __data["historyPeriod"] }
|
||||
public var memberLimit: String { __data["memberLimit"] }
|
||||
public var memberCount: String { __data["memberCount"] }
|
||||
public var overcapacityMemberCount: String { __data["overcapacityMemberCount"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { AuthService } from '@affine/core/modules/cloud/services/auth';
|
||||
import { FetchService } from '@affine/core/modules/cloud/services/fetch';
|
||||
import { AuthStore } from '@affine/core/modules/cloud/stores/auth';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs/services/dialog';
|
||||
import { NbstoreService } from '@affine/core/modules/storage';
|
||||
import { UrlService } from '@affine/core/modules/url/services/url';
|
||||
import { Framework } from '@toeverything/infra';
|
||||
import { of } from 'rxjs';
|
||||
@@ -37,12 +38,16 @@ describe('AuthService oauthPreflight', () => {
|
||||
} as any);
|
||||
framework.service(UrlService, { getClientScheme: () => null } as any);
|
||||
framework.service(GlobalDialogService, { open: vi.fn() } as any);
|
||||
framework.service(NbstoreService, {
|
||||
realtime: { subscribe: () => of() },
|
||||
} as any);
|
||||
|
||||
framework.service(AuthService, [
|
||||
FetchService,
|
||||
AuthStore,
|
||||
UrlService,
|
||||
GlobalDialogService,
|
||||
NbstoreService,
|
||||
]);
|
||||
|
||||
const auth = framework.provider().get(AuthService);
|
||||
|
||||
@@ -31,4 +31,22 @@ describe('CopilotClient action streams', () => {
|
||||
'/api/copilot/actions/session-1/stream?messageId=message-1&actionId=mindmap.generate&actionVersion=v1&runId=run-1&retry=true'
|
||||
);
|
||||
});
|
||||
|
||||
test('loads embedding status through realtime request', async () => {
|
||||
const gql = vi.fn();
|
||||
const request = vi.fn().mockResolvedValue({ total: 4, embedded: 2 });
|
||||
const client = new CopilotClient(gql, vi.fn(), { request });
|
||||
|
||||
await expect(client.getEmbeddingStatus('workspace-1')).resolves.toEqual({
|
||||
total: 4,
|
||||
embedded: 2,
|
||||
});
|
||||
|
||||
expect(gql).not.toHaveBeenCalled();
|
||||
expect(request).toHaveBeenCalledWith(
|
||||
'workspace.embedding.progress.get',
|
||||
{ workspaceId: 'workspace-1' },
|
||||
{ timeoutMs: 10000 }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { showAILoginRequiredAtom } from '@affine/core/components/affine/auth/ai-login-required';
|
||||
import type { AIToolsConfig } from '@affine/core/modules/ai-button';
|
||||
import type { NbstoreService } from '@affine/core/modules/storage';
|
||||
import { UserFriendlyError } from '@affine/error';
|
||||
import {
|
||||
addContextBlobMutation,
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
getCopilotRecentSessionsQuery,
|
||||
getCopilotSessionQuery,
|
||||
getCopilotSessionsQuery,
|
||||
getWorkspaceEmbeddingStatusQuery,
|
||||
type GraphQLQuery,
|
||||
listContextObjectQuery,
|
||||
listContextQuery,
|
||||
@@ -98,7 +98,8 @@ export class CopilotClient {
|
||||
readonly eventSource: (
|
||||
url: string,
|
||||
eventSourceInitDict?: EventSourceInit
|
||||
) => EventSource
|
||||
) => EventSource,
|
||||
readonly realtime?: Pick<NbstoreService['realtime'], 'request'>
|
||||
) {}
|
||||
|
||||
async createSession(
|
||||
@@ -546,11 +547,15 @@ export class CopilotClient {
|
||||
return queryString.toString();
|
||||
}
|
||||
|
||||
getEmbeddingStatus(workspaceId: string) {
|
||||
return this.gql({
|
||||
query: getWorkspaceEmbeddingStatusQuery,
|
||||
variables: { workspaceId },
|
||||
}).then(res => res.queryWorkspaceEmbeddingStatus);
|
||||
async getEmbeddingStatus(workspaceId: string) {
|
||||
if (!this.realtime) {
|
||||
throw new Error('Realtime client is required');
|
||||
}
|
||||
return await this.realtime.request(
|
||||
'workspace.embedding.progress.get',
|
||||
{ workspaceId },
|
||||
{ timeoutMs: 10000 }
|
||||
);
|
||||
}
|
||||
|
||||
addContextBlob(options: OptionsField<typeof addContextBlobMutation>) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { NbstoreService } from '@affine/core/modules/storage';
|
||||
import {
|
||||
ContextCategories,
|
||||
type CopilotChatHistoryFragment,
|
||||
@@ -390,7 +391,8 @@ export function createAIRequestService(
|
||||
eventSource: (
|
||||
url: string,
|
||||
eventSourceInitDict?: EventSourceInit
|
||||
) => EventSource
|
||||
) => EventSource,
|
||||
realtime: Pick<NbstoreService['realtime'], 'request'>
|
||||
) {
|
||||
return new AIRequestService(new CopilotClient(gql, eventSource));
|
||||
return new AIRequestService(new CopilotClient(gql, eventSource, realtime));
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import { DocsService } from '@affine/core/modules/doc';
|
||||
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||
import { useRegisterNavigationCommands } from '@affine/core/modules/navigation/view/use-register-navigation-commands';
|
||||
import { QuickSearchContainer } from '@affine/core/modules/quicksearch';
|
||||
import { NbstoreService } from '@affine/core/modules/storage';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import {
|
||||
getAFFiNEWorkspaceSchema,
|
||||
@@ -140,12 +141,14 @@ export const WorkspaceSideEffects = () => {
|
||||
const graphqlService = useService(GraphQLService);
|
||||
const eventSourceService = useService(EventSourceService);
|
||||
const authService = useService(AuthService);
|
||||
const nbstoreService = useService(NbstoreService);
|
||||
|
||||
useEffect(() => {
|
||||
const dispose = setupAIProvider(
|
||||
createAIRequestService(
|
||||
graphqlService.gql,
|
||||
eventSourceService.eventSource
|
||||
eventSourceService.eventSource,
|
||||
nbstoreService.realtime
|
||||
),
|
||||
globalDialogService,
|
||||
authService
|
||||
@@ -155,6 +158,7 @@ export const WorkspaceSideEffects = () => {
|
||||
};
|
||||
}, [
|
||||
eventSourceService,
|
||||
nbstoreService,
|
||||
workspaceDialogService,
|
||||
graphqlService,
|
||||
globalDialogService,
|
||||
|
||||
+3
-4
@@ -46,13 +46,12 @@ const McpServerSetting = () => {
|
||||
return accessTokens?.find(token => token.name === 'mcp');
|
||||
}, [accessTokens]);
|
||||
|
||||
const displayedToken = revealedAccessToken ?? mcpAccessToken;
|
||||
const hasMcpToken = Boolean(revealedAccessToken || mcpAccessToken);
|
||||
const hasCopyableToken = Boolean(revealedAccessToken);
|
||||
const isRedactedDisplay = hasMcpToken && !hasCopyableToken;
|
||||
|
||||
const code = useMemo(() => {
|
||||
return displayedToken
|
||||
return revealedAccessToken
|
||||
? JSON.stringify(
|
||||
{
|
||||
mcpServers: {
|
||||
@@ -61,7 +60,7 @@ const McpServerSetting = () => {
|
||||
url: `${serverService.server.baseUrl}/api/workspaces/${workspaceService.workspace.id}/mcp`,
|
||||
note: `Read docs from AFFiNE workspace "${workspaceName}"`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${displayedToken.token}`,
|
||||
Authorization: `Bearer ${revealedAccessToken.token}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -70,7 +69,7 @@ const McpServerSetting = () => {
|
||||
2
|
||||
)
|
||||
: null;
|
||||
}, [displayedToken, workspaceName, workspaceService, serverService]);
|
||||
}, [revealedAccessToken, workspaceName, workspaceService, serverService]);
|
||||
|
||||
const copyJsonDisabled = !code || mutating || isRedactedDisplay;
|
||||
const copyJsonTooltip = isRedactedDisplay
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { PeekViewService } from '@affine/core/modules/peek-view';
|
||||
import { NbstoreService } from '@affine/core/modules/storage';
|
||||
import { AppThemeService } from '@affine/core/modules/theme';
|
||||
import {
|
||||
ViewBody,
|
||||
@@ -55,14 +56,16 @@ import * as styles from './index.css';
|
||||
function useAIRequestService() {
|
||||
const graphqlService = useService(GraphQLService);
|
||||
const eventSourceService = useService(EventSourceService);
|
||||
const nbstoreService = useService(NbstoreService);
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
createAIRequestService(
|
||||
graphqlService.gql,
|
||||
eventSourceService.eventSource
|
||||
eventSourceService.eventSource,
|
||||
nbstoreService.realtime
|
||||
),
|
||||
[graphqlService, eventSourceService]
|
||||
[graphqlService, eventSourceService, nbstoreService]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { useSignalValue } from '@affine/core/modules/doc-info/utils';
|
||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||
import { PeekViewService } from '@affine/core/modules/peek-view';
|
||||
import { NbstoreService } from '@affine/core/modules/storage';
|
||||
import { AppThemeService } from '@affine/core/modules/theme';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
@@ -75,6 +76,7 @@ export const EditorChatPanel = ({
|
||||
const framework = useFramework();
|
||||
const graphqlService = useService(GraphQLService);
|
||||
const eventSourceService = useService(EventSourceService);
|
||||
const nbstoreService = useService(NbstoreService);
|
||||
const workbench = useService(WorkbenchService).workbench;
|
||||
const t = useI18n();
|
||||
|
||||
@@ -108,9 +110,10 @@ export const EditorChatPanel = ({
|
||||
() =>
|
||||
createAIRequestService(
|
||||
graphqlService.gql,
|
||||
eventSourceService.eventSource
|
||||
eventSourceService.eventSource,
|
||||
nbstoreService.realtime
|
||||
),
|
||||
[eventSourceService.eventSource, graphqlService.gql]
|
||||
[eventSourceService.eventSource, graphqlService.gql, nbstoreService]
|
||||
);
|
||||
|
||||
const [pendingSessionId] = useState(() => {
|
||||
|
||||
@@ -12,8 +12,8 @@ import {
|
||||
} from '@toeverything/infra';
|
||||
import { map, tap } from 'rxjs';
|
||||
|
||||
import { mapRealtimeEnum } from '../realtime/enum';
|
||||
import type { AuthService } from '../services/auth';
|
||||
import type { UserFeatureStore } from '../stores/user-feature';
|
||||
|
||||
export class UserFeature extends Entity {
|
||||
// undefined means no user, null means loading
|
||||
@@ -32,10 +32,7 @@ export class UserFeature extends Entity {
|
||||
isRevalidating$ = new LiveData(false);
|
||||
error$ = new LiveData<any | null>(null);
|
||||
|
||||
constructor(
|
||||
private readonly authService: AuthService,
|
||||
private readonly store: UserFeatureStore
|
||||
) {
|
||||
constructor(private readonly authService: AuthService) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -44,23 +41,20 @@ export class UserFeature extends Entity {
|
||||
accountId: this.authService.session.account$.value?.id,
|
||||
})),
|
||||
exhaustMapSwitchUntilChanged(
|
||||
(a, b) => a.accountId === b.accountId,
|
||||
() => false,
|
||||
({ accountId }) => {
|
||||
return fromPromise(async signal => {
|
||||
return fromPromise(async () => {
|
||||
if (!accountId) {
|
||||
return; // no feature if no user
|
||||
}
|
||||
|
||||
const { userId, features } = await this.store.getUserFeatures(signal);
|
||||
if (userId !== accountId) {
|
||||
// The user has changed, ignore the result
|
||||
this.authService.session.revalidate();
|
||||
await this.authService.session.waitForRevalidation();
|
||||
return;
|
||||
}
|
||||
const account = this.authService.session.account$.value;
|
||||
if (account?.id !== accountId) return;
|
||||
return {
|
||||
userId: userId,
|
||||
features: features,
|
||||
userId: account.id,
|
||||
features: account.info?.features?.map(feature =>
|
||||
mapRealtimeEnum(FeatureType, feature, 'user feature')
|
||||
),
|
||||
};
|
||||
}).pipe(
|
||||
smartRetry(),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { GetCurrentUserProfileQuery } from '@affine/graphql';
|
||||
import type { UserQuotaStateSnapshot } from '@affine/realtime';
|
||||
import { Framework } from '@toeverything/infra';
|
||||
import { Subject } from 'rxjs';
|
||||
@@ -8,8 +7,6 @@ import { AuthService } from '../services/auth';
|
||||
import { UserQuotaStore } from '../stores/user-quota';
|
||||
import { UserQuota } from './user-quota';
|
||||
|
||||
type Quota = NonNullable<GetCurrentUserProfileQuery['currentUser']>['quota'];
|
||||
|
||||
const authService = {
|
||||
session: {
|
||||
['account$']: {
|
||||
@@ -41,24 +38,6 @@ function createQuotaState(
|
||||
};
|
||||
}
|
||||
|
||||
function createQuota(overrides: Partial<Quota> = {}): Quota {
|
||||
return {
|
||||
name: 'Legacy',
|
||||
blobLimit: 1024,
|
||||
storageQuota: 2048,
|
||||
historyPeriod: 30 * 24 * 60 * 60,
|
||||
memberLimit: 8,
|
||||
humanReadable: {
|
||||
name: 'Legacy',
|
||||
blobLimit: '1 KB',
|
||||
storageQuota: '2 KB',
|
||||
historyPeriod: '30 days',
|
||||
memberLimit: '8',
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function createStore(
|
||||
overrides: Partial<UserQuotaStore>,
|
||||
eventSubject = new Subject<{ type: 'ready' } | { changed: true }>()
|
||||
@@ -66,7 +45,6 @@ function createStore(
|
||||
return {
|
||||
fetchUserQuotaState: vi.fn(),
|
||||
subscribeUserQuotaState: vi.fn(() => eventSubject),
|
||||
fetchUserQuota: vi.fn(),
|
||||
...overrides,
|
||||
} as unknown as UserQuotaStore;
|
||||
}
|
||||
@@ -103,24 +81,20 @@ describe('UserQuota', () => {
|
||||
await vi.waitFor(() => expect(quota.used$.value).toBe(768));
|
||||
|
||||
expect(store.fetchUserQuotaState).toHaveBeenCalledTimes(2);
|
||||
expect(store.fetchUserQuota).not.toHaveBeenCalled();
|
||||
quota.dispose();
|
||||
});
|
||||
|
||||
test('falls back to legacy GraphQL quota when realtime request fails', async () => {
|
||||
test('surfaces realtime quota errors without GraphQL fallback', async () => {
|
||||
const error = new Error('offline');
|
||||
const store = createStore({
|
||||
fetchUserQuotaState: vi.fn().mockRejectedValue(new Error('offline')),
|
||||
fetchUserQuota: vi.fn().mockResolvedValue({
|
||||
quota: createQuota(),
|
||||
used: 256,
|
||||
}),
|
||||
fetchUserQuotaState: vi.fn().mockRejectedValue(error),
|
||||
});
|
||||
const quota = createEntity(store);
|
||||
|
||||
quota.revalidate();
|
||||
|
||||
await vi.waitFor(() => expect(quota.quota$.value?.name).toBe('Legacy'));
|
||||
expect(quota.used$.value).toBe(256);
|
||||
await vi.waitFor(() => expect(quota.error$.value).toBe(error));
|
||||
expect(quota.quota$.value).toBeNull();
|
||||
quota.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { GetCurrentUserProfileQuery } from '@affine/graphql';
|
||||
import type {
|
||||
RealtimeTopicEventOf,
|
||||
UserQuotaStateSnapshot,
|
||||
@@ -11,9 +10,20 @@ import { RealtimeLiveQuery } from '../realtime/live-query';
|
||||
import type { AuthService } from '../services/auth';
|
||||
import type { UserQuotaStore } from '../stores/user-quota';
|
||||
|
||||
type QuotaType = NonNullable<
|
||||
GetCurrentUserProfileQuery['currentUser']
|
||||
>['quota'];
|
||||
type QuotaType = {
|
||||
name: string;
|
||||
blobLimit: number;
|
||||
storageQuota: number;
|
||||
historyPeriod: number;
|
||||
memberLimit: number;
|
||||
humanReadable: {
|
||||
name: string;
|
||||
blobLimit: string;
|
||||
storageQuota: string;
|
||||
historyPeriod: string;
|
||||
memberLimit: string;
|
||||
};
|
||||
};
|
||||
|
||||
const DAY_SECONDS = 24 * 60 * 60;
|
||||
|
||||
@@ -159,9 +169,6 @@ export class UserQuota extends Entity {
|
||||
quota: userQuotaFromState(state),
|
||||
used: state.usedStorageQuota,
|
||||
};
|
||||
} catch {
|
||||
const { quota, used } = await this.store.fetchUserQuota(signal);
|
||||
return { quota, used };
|
||||
} finally {
|
||||
this.isRevalidating$.setValue(false);
|
||||
}
|
||||
|
||||
@@ -101,7 +101,6 @@ import { ServerConfigStore } from './stores/server-config';
|
||||
import { ServerListStore } from './stores/server-list';
|
||||
import { SubscriptionStore } from './stores/subscription';
|
||||
import { UserCopilotQuotaStore } from './stores/user-copilot-quota';
|
||||
import { UserFeatureStore } from './stores/user-feature';
|
||||
import { UserQuotaStore } from './stores/user-quota';
|
||||
import { UserSettingsStore } from './stores/user-settings';
|
||||
import { DocCreatedByService } from './services/doc-created-by';
|
||||
@@ -146,6 +145,7 @@ export function configureCloudModule(framework: Framework) {
|
||||
AuthStore,
|
||||
UrlService,
|
||||
GlobalDialogService,
|
||||
NbstoreService,
|
||||
])
|
||||
.store(AuthStore, [
|
||||
FetchService,
|
||||
@@ -153,6 +153,7 @@ export function configureCloudModule(framework: Framework) {
|
||||
GlobalState,
|
||||
ServerService,
|
||||
AuthProvider,
|
||||
NbstoreService,
|
||||
])
|
||||
.entity(AuthSession, [AuthStore])
|
||||
.service(SubscriptionService, [SubscriptionStore])
|
||||
@@ -165,7 +166,7 @@ export function configureCloudModule(framework: Framework) {
|
||||
.entity(Subscription, [AuthService, ServerService, SubscriptionStore])
|
||||
.entity(SubscriptionPrices, [ServerService, SubscriptionStore])
|
||||
.service(UserQuotaService)
|
||||
.store(UserQuotaStore, [GraphQLService, NbstoreService])
|
||||
.store(UserQuotaStore, [NbstoreService])
|
||||
.entity(UserQuota, [AuthService, UserQuotaStore])
|
||||
.service(UserCopilotQuotaService)
|
||||
.store(UserCopilotQuotaStore, [GraphQLService])
|
||||
@@ -175,8 +176,7 @@ export function configureCloudModule(framework: Framework) {
|
||||
ServerService,
|
||||
])
|
||||
.service(UserFeatureService)
|
||||
.entity(UserFeature, [AuthService, UserFeatureStore])
|
||||
.store(UserFeatureStore, [GraphQLService])
|
||||
.entity(UserFeature, [AuthService])
|
||||
.service(InvoicesService)
|
||||
.store(InvoicesStore, [GraphQLService])
|
||||
.entity(Invoices, [InvoicesStore])
|
||||
@@ -188,9 +188,9 @@ export function configureCloudModule(framework: Framework) {
|
||||
.service(PublicUserService, [PublicUserStore])
|
||||
.store(PublicUserStore, [GraphQLService])
|
||||
.service(UserSettingsService, [UserSettingsStore])
|
||||
.store(UserSettingsStore, [GraphQLService])
|
||||
.store(UserSettingsStore, [GraphQLService, NbstoreService])
|
||||
.service(AccessTokenService, [AccessTokenStore])
|
||||
.store(AccessTokenStore, [GraphQLService]);
|
||||
.store(AccessTokenStore, [GraphQLService, NbstoreService]);
|
||||
|
||||
framework
|
||||
.scope(WorkspaceScope)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export function mapRealtimeEnum<T extends Record<string, string>>(
|
||||
enumType: T,
|
||||
value: string,
|
||||
label: string
|
||||
): T[keyof T] {
|
||||
if (Object.prototype.hasOwnProperty.call(enumType, value)) {
|
||||
return enumType[value as keyof T];
|
||||
}
|
||||
throw new Error(`Unknown ${label}: ${value}`);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Framework } from '@toeverything/infra';
|
||||
import { Subject } from 'rxjs';
|
||||
import { describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { AccessTokenStore } from '../stores/access-token';
|
||||
import { AccessTokenService } from './access-token';
|
||||
|
||||
function createStore() {
|
||||
return {
|
||||
subscribeUserAccessTokens: vi.fn(() => new Subject()),
|
||||
listUserAccessTokens: vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: 'token-1',
|
||||
name: 'MCP',
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
expiresAt: null,
|
||||
},
|
||||
]),
|
||||
generateUserAccessToken: vi.fn().mockResolvedValue({
|
||||
id: 'token-1',
|
||||
name: 'MCP',
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
expiresAt: null,
|
||||
token: 'secret-token',
|
||||
}),
|
||||
} as unknown as AccessTokenStore;
|
||||
}
|
||||
|
||||
describe('AccessTokenService', () => {
|
||||
test('does not store generated plaintext token in the long-lived list', async () => {
|
||||
const framework = new Framework();
|
||||
framework
|
||||
.store(AccessTokenStore, createStore())
|
||||
.service(AccessTokenService, [AccessTokenStore]);
|
||||
const service = framework.provider().get(AccessTokenService);
|
||||
|
||||
const accessToken = await service.generateUserAccessToken('MCP');
|
||||
|
||||
expect(accessToken.token).toBe('secret-token');
|
||||
expect(service.accessTokens$.value).toEqual([
|
||||
{
|
||||
id: 'token-1',
|
||||
name: 'MCP',
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
expiresAt: null,
|
||||
},
|
||||
]);
|
||||
expect(JSON.stringify(service.accessTokens$.value)).not.toContain(
|
||||
'secret-token'
|
||||
);
|
||||
|
||||
service.dispose();
|
||||
});
|
||||
});
|
||||
@@ -1,40 +1,48 @@
|
||||
import {
|
||||
effect,
|
||||
exhaustMapWithTrailing,
|
||||
fromPromise,
|
||||
LiveData,
|
||||
onComplete,
|
||||
OnEvent,
|
||||
onStart,
|
||||
Service,
|
||||
smartRetry,
|
||||
} from '@toeverything/infra';
|
||||
import { catchError, EMPTY, tap } from 'rxjs';
|
||||
import { LiveData, OnEvent, Service } from '@toeverything/infra';
|
||||
|
||||
import { AccountChanged } from '../events/account-changed';
|
||||
import type { AccessToken, AccessTokenStore } from '../stores/access-token';
|
||||
import { RealtimeLiveQuery } from '../realtime/live-query';
|
||||
import type {
|
||||
AccessToken,
|
||||
AccessTokenStore,
|
||||
ListedAccessToken,
|
||||
} from '../stores/access-token';
|
||||
|
||||
@OnEvent(AccountChanged, e => e.onAccountChanged)
|
||||
export class AccessTokenService extends Service {
|
||||
constructor(private readonly accessTokenStore: AccessTokenStore) {
|
||||
super();
|
||||
this.liveQuery.start();
|
||||
}
|
||||
|
||||
accessTokens$ = new LiveData<AccessToken[] | null>(null);
|
||||
accessTokens$ = new LiveData<ListedAccessToken[] | null>(null);
|
||||
isRevalidating$ = new LiveData(false);
|
||||
error$ = new LiveData<any>(null);
|
||||
private readonly liveQuery = new RealtimeLiveQuery({
|
||||
request: signal => this.requestAccessTokens(signal),
|
||||
subscribe: () => this.accessTokenStore.subscribeUserAccessTokens(),
|
||||
applySnapshot: accessTokens => {
|
||||
this.error$.value = null;
|
||||
this.accessTokens$.value = accessTokens;
|
||||
},
|
||||
applyEvent: () => 'revalidate' as const,
|
||||
onError: error => {
|
||||
this.error$.value = error;
|
||||
},
|
||||
});
|
||||
|
||||
async generateUserAccessToken(name: string): Promise<AccessToken> {
|
||||
const accessToken =
|
||||
await this.accessTokenStore.generateUserAccessToken(name);
|
||||
const { token: _token, ...listedAccessToken } = accessToken;
|
||||
this.accessTokens$.value = [
|
||||
...(this.accessTokens$.value || []),
|
||||
accessToken as AccessToken,
|
||||
listedAccessToken,
|
||||
];
|
||||
|
||||
await this.waitForRevalidation();
|
||||
|
||||
return accessToken as AccessToken;
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
async revokeUserAccessToken(id: string) {
|
||||
@@ -44,28 +52,9 @@ export class AccessTokenService extends Service {
|
||||
await this.waitForRevalidation();
|
||||
}
|
||||
|
||||
revalidate = effect(
|
||||
exhaustMapWithTrailing(() => {
|
||||
return fromPromise(() => {
|
||||
return this.accessTokenStore.listUserAccessTokens();
|
||||
}).pipe(
|
||||
smartRetry(),
|
||||
tap(accessTokens => {
|
||||
this.accessTokens$.value = accessTokens;
|
||||
}),
|
||||
catchError(error => {
|
||||
this.error$.value = error;
|
||||
return EMPTY;
|
||||
}),
|
||||
onStart(() => {
|
||||
this.isRevalidating$.value = true;
|
||||
}),
|
||||
onComplete(() => {
|
||||
this.isRevalidating$.value = false;
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
revalidate = () => {
|
||||
this.liveQuery.revalidate();
|
||||
};
|
||||
|
||||
private onAccountChanged() {
|
||||
this.accessTokens$.value = null;
|
||||
@@ -79,4 +68,18 @@ export class AccessTokenService extends Service {
|
||||
signal
|
||||
);
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
super.dispose();
|
||||
this.liveQuery.dispose();
|
||||
}
|
||||
|
||||
private async requestAccessTokens(signal: AbortSignal) {
|
||||
this.isRevalidating$.value = true;
|
||||
try {
|
||||
return await this.accessTokenStore.listUserAccessTokens(signal);
|
||||
} finally {
|
||||
this.isRevalidating$.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@ import type { OAuthProviderType } from '@affine/graphql';
|
||||
import { track } from '@affine/track';
|
||||
import { OnEvent, Service } from '@toeverything/infra';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { distinctUntilChanged, map, skip } from 'rxjs';
|
||||
import { distinctUntilChanged, map, skip, type Subscription } from 'rxjs';
|
||||
|
||||
import type { GlobalDialogService } from '../../dialogs';
|
||||
import { ApplicationFocused } from '../../lifecycle';
|
||||
import type { NbstoreService } from '../../storage';
|
||||
import type { UrlService } from '../../url';
|
||||
import { AuthSession } from '../entities/session';
|
||||
import { AccountChanged } from '../events/account-changed';
|
||||
@@ -20,12 +21,14 @@ import type { FetchService } from './fetch';
|
||||
@OnEvent(ServerStarted, e => e.onServerStarted)
|
||||
export class AuthService extends Service {
|
||||
session = this.framework.createEntity(AuthSession);
|
||||
private profileSubscription?: Subscription;
|
||||
|
||||
constructor(
|
||||
private readonly fetchService: FetchService,
|
||||
private readonly store: AuthStore,
|
||||
private readonly urlService: UrlService,
|
||||
private readonly dialogService: GlobalDialogService
|
||||
private readonly dialogService: GlobalDialogService,
|
||||
private readonly nbstoreService: NbstoreService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -41,21 +44,53 @@ export class AuthService extends Service {
|
||||
.subscribe(({ account }) => {
|
||||
if (account === null) {
|
||||
this.eventBus.emit(AccountLoggedOut, account);
|
||||
this.profileSubscription?.unsubscribe();
|
||||
this.profileSubscription = undefined;
|
||||
} else {
|
||||
this.eventBus.emit(AccountLoggedIn, account);
|
||||
this.subscribeProfile();
|
||||
}
|
||||
this.eventBus.emit(AccountChanged, account);
|
||||
});
|
||||
|
||||
this.subscribeProfile();
|
||||
}
|
||||
|
||||
private onServerStarted() {
|
||||
this.session.revalidate();
|
||||
this.subscribeProfile();
|
||||
}
|
||||
|
||||
private onApplicationFocused() {
|
||||
this.session.revalidate();
|
||||
}
|
||||
|
||||
private subscribeProfile() {
|
||||
this.profileSubscription?.unsubscribe();
|
||||
this.profileSubscription = undefined;
|
||||
if (!this.session.account$.value) return;
|
||||
this.profileSubscription = this.nbstoreService.realtime
|
||||
.subscribe('user.profile.changed', {})
|
||||
.subscribe({
|
||||
next: () => {
|
||||
void (async () => {
|
||||
this.session.revalidate();
|
||||
await this.session.waitForRevalidation();
|
||||
this.eventBus.emit(AccountChanged, this.session.account$.value);
|
||||
})().catch(() => {});
|
||||
},
|
||||
error: () => {
|
||||
this.profileSubscription = undefined;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
override dispose() {
|
||||
super.dispose();
|
||||
this.profileSubscription?.unsubscribe();
|
||||
this.profileSubscription = undefined;
|
||||
}
|
||||
|
||||
async sendEmailMagicLink(
|
||||
email: string,
|
||||
verifyToken?: string,
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
import {
|
||||
effect,
|
||||
exhaustMapWithTrailing,
|
||||
fromPromise,
|
||||
LiveData,
|
||||
onComplete,
|
||||
onStart,
|
||||
Service,
|
||||
smartRetry,
|
||||
} from '@toeverything/infra';
|
||||
import { catchError, EMPTY, tap } from 'rxjs';
|
||||
import { LiveData, Service } from '@toeverything/infra';
|
||||
|
||||
import { RealtimeLiveQuery } from '../realtime/live-query';
|
||||
import type {
|
||||
UpdateUserSettingsInput,
|
||||
UserSettings,
|
||||
@@ -21,34 +12,28 @@ export type { UserSettings };
|
||||
export class UserSettingsService extends Service {
|
||||
constructor(private readonly store: UserSettingsStore) {
|
||||
super();
|
||||
this.liveQuery.start();
|
||||
}
|
||||
|
||||
userSettings$ = new LiveData<UserSettings | undefined>(undefined);
|
||||
isLoading$ = new LiveData<boolean>(false);
|
||||
error$ = new LiveData<any | undefined>(undefined);
|
||||
private readonly liveQuery = new RealtimeLiveQuery({
|
||||
request: signal => this.requestUserSettings(signal),
|
||||
subscribe: () => this.store.subscribeUserSettings(),
|
||||
applySnapshot: settings => {
|
||||
this.error$.value = undefined;
|
||||
this.userSettings$.value = settings;
|
||||
},
|
||||
applyEvent: () => 'revalidate' as const,
|
||||
onError: error => {
|
||||
this.error$.value = error;
|
||||
},
|
||||
});
|
||||
|
||||
revalidate = effect(
|
||||
exhaustMapWithTrailing(() => {
|
||||
return fromPromise(() => {
|
||||
return this.store.getUserSettings();
|
||||
}).pipe(
|
||||
smartRetry(),
|
||||
tap(settings => {
|
||||
this.userSettings$.value = settings;
|
||||
}),
|
||||
catchError(error => {
|
||||
this.error$.value = error;
|
||||
return EMPTY;
|
||||
}),
|
||||
onStart(() => {
|
||||
this.isLoading$.value = true;
|
||||
}),
|
||||
onComplete(() => {
|
||||
this.isLoading$.value = false;
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
revalidate = () => {
|
||||
this.liveQuery.revalidate();
|
||||
};
|
||||
|
||||
async updateUserSettings(settings: UpdateUserSettingsInput) {
|
||||
await this.store.updateUserSettings(settings);
|
||||
@@ -58,4 +43,18 @@ export class UserSettingsService extends Service {
|
||||
};
|
||||
this.revalidate();
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
super.dispose();
|
||||
this.liveQuery.dispose();
|
||||
}
|
||||
|
||||
private async requestUserSettings(signal: AbortSignal) {
|
||||
this.isLoading$.value = true;
|
||||
try {
|
||||
return await this.store.getUserSettings(signal);
|
||||
} finally {
|
||||
this.isLoading$.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,40 @@
|
||||
import {
|
||||
generateUserAccessTokenMutation,
|
||||
type ListUserAccessTokensQuery,
|
||||
listUserAccessTokensQuery,
|
||||
revokeUserAccessTokenMutation,
|
||||
} from '@affine/graphql';
|
||||
import type { AccessTokenSnapshot } from '@affine/realtime';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { NbstoreService } from '../../storage';
|
||||
import type { GraphQLService } from '../services/graphql';
|
||||
|
||||
export type AccessToken = NonNullable<
|
||||
ListUserAccessTokensQuery['currentUser']
|
||||
>['revealedAccessTokens'][number];
|
||||
export type AccessToken = AccessTokenSnapshot & { token: string };
|
||||
export type ListedAccessToken = AccessTokenSnapshot;
|
||||
|
||||
export class AccessTokenStore extends Store {
|
||||
constructor(private readonly gqlService: GraphQLService) {
|
||||
constructor(
|
||||
private readonly gqlService: GraphQLService,
|
||||
private readonly nbstoreService: NbstoreService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async listUserAccessTokens(signal?: AbortSignal): Promise<AccessToken[]> {
|
||||
const data = await this.gqlService.gql({
|
||||
query: listUserAccessTokensQuery,
|
||||
context: { signal },
|
||||
});
|
||||
async listUserAccessTokens(
|
||||
signal?: AbortSignal
|
||||
): Promise<ListedAccessToken[]> {
|
||||
const { tokens } = await this.nbstoreService.realtime.request(
|
||||
'user.access-tokens.get',
|
||||
{},
|
||||
{ signal, timeoutMs: 10000 }
|
||||
);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
return data.currentUser?.revealedAccessTokens ?? [];
|
||||
subscribeUserAccessTokens() {
|
||||
return this.nbstoreService.realtime.subscribe(
|
||||
'user.access-tokens.changed',
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
async generateUserAccessToken(
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import {
|
||||
deleteAccountMutation,
|
||||
removeAvatarMutation,
|
||||
ServerDeploymentType,
|
||||
updateUserProfileMutation,
|
||||
uploadAvatarMutation,
|
||||
} from '@affine/graphql';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { GlobalState } from '../../storage';
|
||||
import type { GlobalState, NbstoreService } from '../../storage';
|
||||
import type { AuthSessionInfo } from '../entities/session';
|
||||
import type { AuthProvider } from '../provider/auth';
|
||||
import type { FetchService } from '../services/fetch';
|
||||
@@ -20,6 +21,7 @@ export interface AccountProfile {
|
||||
hasPassword: boolean;
|
||||
avatarUrl: string | null;
|
||||
emailVerified: string | null;
|
||||
features?: string[];
|
||||
}
|
||||
|
||||
export class AuthStore extends Store {
|
||||
@@ -28,7 +30,8 @@ export class AuthStore extends Store {
|
||||
private readonly gqlService: GraphQLService,
|
||||
private readonly globalState: GlobalState,
|
||||
private readonly serverService: ServerService,
|
||||
private readonly authProvider: AuthProvider
|
||||
private readonly authProvider: AuthProvider,
|
||||
private readonly nbstoreService: NbstoreService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -58,20 +61,20 @@ export class AuthStore extends Store {
|
||||
}
|
||||
|
||||
async fetchSession() {
|
||||
const url = `/api/auth/session`;
|
||||
const options: RequestInit = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
const { user } = await this.nbstoreService.realtime.request(
|
||||
'user.profile.get',
|
||||
{},
|
||||
{ timeoutMs: 10000 }
|
||||
);
|
||||
return {
|
||||
user: user
|
||||
? {
|
||||
...user,
|
||||
hasPassword: Boolean(user.hasPassword),
|
||||
emailVerified: user.emailVerified ? 'true' : null,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
|
||||
const res = await this.fetchService.fetch(url, options);
|
||||
const data = (await res.json()) as {
|
||||
user?: AccountProfile | null;
|
||||
};
|
||||
if (!res.ok)
|
||||
throw new Error('Get session fetch error: ' + JSON.stringify(data));
|
||||
return data; // Return null if data empty
|
||||
}
|
||||
|
||||
async signInMagicLink(email: string, token: string) {
|
||||
@@ -102,6 +105,13 @@ export class AuthStore extends Store {
|
||||
|
||||
async signOut() {
|
||||
await this.authProvider.signOut();
|
||||
await this.nbstoreService.realtime.configure({
|
||||
endpoint: this.serverService.server.baseUrl,
|
||||
authenticated: false,
|
||||
isSelfHosted:
|
||||
this.serverService.server.config$.value.type ===
|
||||
ServerDeploymentType.Selfhosted,
|
||||
});
|
||||
}
|
||||
|
||||
async uploadAvatar(file: File) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getCurrentUserProfileQuery } from '@affine/graphql';
|
||||
import { copilotQuotaQuery } from '@affine/graphql';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { GraphQLService } from '../services/graphql';
|
||||
@@ -10,7 +10,7 @@ export class UserCopilotQuotaStore extends Store {
|
||||
|
||||
async fetchUserCopilotQuota(abortSignal?: AbortSignal) {
|
||||
const data = await this.graphqlService.gql({
|
||||
query: getCurrentUserProfileQuery,
|
||||
query: copilotQuotaQuery,
|
||||
context: {
|
||||
signal: abortSignal,
|
||||
},
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { getCurrentUserProfileQuery } from '@affine/graphql';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { GraphQLService } from '../services/graphql';
|
||||
|
||||
export class UserFeatureStore extends Store {
|
||||
constructor(private readonly gqlService: GraphQLService) {
|
||||
super();
|
||||
}
|
||||
|
||||
async getUserFeatures(signal: AbortSignal) {
|
||||
const data = await this.gqlService.gql({
|
||||
query: getCurrentUserProfileQuery,
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
});
|
||||
return {
|
||||
userId: data.currentUser?.id,
|
||||
features: data.currentUser?.features,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,9 @@
|
||||
import { getCurrentUserProfileQuery } from '@affine/graphql';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { NbstoreService } from '../../storage';
|
||||
import type { GraphQLService } from '../services/graphql';
|
||||
|
||||
export class UserQuotaStore extends Store {
|
||||
constructor(
|
||||
private readonly graphqlService: GraphQLService,
|
||||
private readonly nbstoreService: NbstoreService
|
||||
) {
|
||||
constructor(private readonly nbstoreService: NbstoreService) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -27,23 +22,4 @@ export class UserQuotaStore extends Store {
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
async fetchUserQuota(abortSignal?: AbortSignal) {
|
||||
const data = await this.graphqlService.gql({
|
||||
query: getCurrentUserProfileQuery,
|
||||
context: {
|
||||
signal: abortSignal,
|
||||
},
|
||||
});
|
||||
|
||||
if (!data.currentUser) {
|
||||
throw new Error('No logged in');
|
||||
}
|
||||
|
||||
return {
|
||||
userId: data.currentUser.id,
|
||||
quota: data.currentUser.quota,
|
||||
used: data.currentUser.quotaUsage.storageQuota,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,38 @@
|
||||
import {
|
||||
type GetCurrentUserProfileQuery,
|
||||
getCurrentUserProfileQuery,
|
||||
type UpdateUserSettingsInput,
|
||||
updateUserSettingsMutation,
|
||||
} from '@affine/graphql';
|
||||
import type { UserSettingsSnapshot } from '@affine/realtime';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { NbstoreService } from '../../storage';
|
||||
import type { GraphQLService } from '../services/graphql';
|
||||
|
||||
export type UserSettings = NonNullable<
|
||||
GetCurrentUserProfileQuery['currentUser']
|
||||
>['settings'];
|
||||
export type UserSettings = UserSettingsSnapshot;
|
||||
|
||||
export type { UpdateUserSettingsInput };
|
||||
|
||||
export class UserSettingsStore extends Store {
|
||||
constructor(private readonly gqlService: GraphQLService) {
|
||||
constructor(
|
||||
private readonly gqlService: GraphQLService,
|
||||
private readonly nbstoreService: NbstoreService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async getUserSettings(): Promise<UserSettings | undefined> {
|
||||
const result = await this.gqlService.gql({
|
||||
query: getCurrentUserProfileQuery,
|
||||
});
|
||||
return result.currentUser?.settings;
|
||||
async getUserSettings(
|
||||
signal?: AbortSignal
|
||||
): Promise<UserSettings | undefined> {
|
||||
const { settings } = await this.nbstoreService.realtime.request(
|
||||
'user.settings.get',
|
||||
{},
|
||||
{ signal, timeoutMs: 10000 }
|
||||
);
|
||||
return settings;
|
||||
}
|
||||
|
||||
subscribeUserSettings() {
|
||||
return this.nbstoreService.realtime.subscribe('user.settings.changed', {});
|
||||
}
|
||||
|
||||
async updateUserSettings(settings: UpdateUserSettingsInput) {
|
||||
|
||||
@@ -150,7 +150,8 @@ export class DocCommentStore extends Entity<{
|
||||
return {
|
||||
changes: commentChanges.changes.map(change => ({
|
||||
id: change.id,
|
||||
action: change.action,
|
||||
action:
|
||||
change.action as DocCommentChangeListResult['changes'][number]['action'],
|
||||
comment: normalizeComment(change.item as GQLCommentType),
|
||||
commentId: change.commentId ?? undefined,
|
||||
})),
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
import type { GetMembersByWorkspaceIdQuery } from '@affine/graphql';
|
||||
import {
|
||||
catchErrorInto,
|
||||
effect,
|
||||
Entity,
|
||||
fromPromise,
|
||||
LiveData,
|
||||
onComplete,
|
||||
onStart,
|
||||
smartRetry,
|
||||
} from '@toeverything/infra';
|
||||
import { map, switchMap, tap } from 'rxjs';
|
||||
import type { WorkspaceMemberStatus } from '@affine/graphql';
|
||||
import type {
|
||||
RealtimeTopicEventOf,
|
||||
WorkspaceMemberSnapshot,
|
||||
} from '@affine/realtime';
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
|
||||
import { RealtimeLiveQuery } from '../../cloud/realtime/live-query';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import type { WorkspaceMembersStore } from '../stores/members';
|
||||
|
||||
export type Member =
|
||||
GetMembersByWorkspaceIdQuery['workspace']['members'][number];
|
||||
export type Member = Omit<
|
||||
WorkspaceMemberSnapshot,
|
||||
'permission' | 'role' | 'status'
|
||||
> & {
|
||||
permission: string;
|
||||
role: string;
|
||||
status: WorkspaceMemberStatus;
|
||||
};
|
||||
|
||||
export class WorkspaceMembers extends Entity {
|
||||
constructor(
|
||||
@@ -23,6 +24,7 @@ export class WorkspaceMembers extends Entity {
|
||||
private readonly workspaceService: WorkspaceService
|
||||
) {
|
||||
super();
|
||||
this.liveQuery.start();
|
||||
}
|
||||
|
||||
pageNum$ = new LiveData(0);
|
||||
@@ -34,37 +36,48 @@ export class WorkspaceMembers extends Entity {
|
||||
|
||||
readonly PAGE_SIZE = 8;
|
||||
|
||||
readonly revalidate = effect(
|
||||
map(() => this.pageNum$.value),
|
||||
switchMap(pageNum => {
|
||||
return fromPromise(async signal => {
|
||||
return this.store.fetchMembers(
|
||||
this.workspaceService.workspace.id,
|
||||
pageNum * this.PAGE_SIZE,
|
||||
this.PAGE_SIZE,
|
||||
signal
|
||||
);
|
||||
}).pipe(
|
||||
tap(data => {
|
||||
this.memberCount$.setValue(data.memberCount);
|
||||
this.pageMembers$.setValue(data.members);
|
||||
}),
|
||||
smartRetry(),
|
||||
catchErrorInto(this.error$),
|
||||
onStart(() => {
|
||||
this.pageMembers$.setValue(undefined);
|
||||
this.isLoading$.setValue(true);
|
||||
}),
|
||||
onComplete(() => this.isLoading$.setValue(false))
|
||||
);
|
||||
})
|
||||
);
|
||||
private readonly liveQuery = new RealtimeLiveQuery<
|
||||
{ members: Member[]; memberCount: number },
|
||||
RealtimeTopicEventOf<'workspace.members.changed'>
|
||||
>({
|
||||
request: signal => this.requestMembers(signal),
|
||||
subscribe: () =>
|
||||
this.store.subscribeMembers(this.workspaceService.workspace.id),
|
||||
applySnapshot: data => {
|
||||
this.error$.next(null);
|
||||
this.memberCount$.setValue(data.memberCount);
|
||||
this.pageMembers$.setValue(data.members);
|
||||
},
|
||||
applyEvent: () => 'revalidate',
|
||||
onError: error => this.error$.setValue(error),
|
||||
});
|
||||
|
||||
revalidate = () => {
|
||||
this.liveQuery.revalidate();
|
||||
};
|
||||
|
||||
setPageNum(pageNum: number) {
|
||||
this.pageNum$.setValue(pageNum);
|
||||
this.revalidate();
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this.revalidate.unsubscribe();
|
||||
this.liveQuery.dispose();
|
||||
}
|
||||
|
||||
private async requestMembers(signal: AbortSignal) {
|
||||
this.pageMembers$.setValue(undefined);
|
||||
this.isLoading$.setValue(true);
|
||||
try {
|
||||
const pageNum = this.pageNum$.value;
|
||||
return await this.store.fetchMembers(
|
||||
this.workspaceService.workspace.id,
|
||||
pageNum * this.PAGE_SIZE,
|
||||
this.PAGE_SIZE,
|
||||
signal
|
||||
);
|
||||
} finally {
|
||||
this.isLoading$.setValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
onComplete,
|
||||
onStart,
|
||||
} from '@toeverything/infra';
|
||||
import type { Subscription } from 'rxjs';
|
||||
import { tap } from 'rxjs';
|
||||
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
@@ -25,12 +26,24 @@ export class WorkspacePermission extends Entity {
|
||||
);
|
||||
isTeam$ = this.cache$.map(cache => cache?.isTeam ?? null);
|
||||
isRevalidating$ = new LiveData(false);
|
||||
private readonly subscription?: Subscription;
|
||||
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly store: WorkspacePermissionStore
|
||||
) {
|
||||
super();
|
||||
if (
|
||||
this.workspaceService.workspace.flavour !== 'local' &&
|
||||
!this.workspaceService.workspace.openOptions.isSharedMode
|
||||
) {
|
||||
this.subscription = this.store
|
||||
.subscribeWorkspaceAccess(this.workspaceService.workspace.id)
|
||||
.subscribe({
|
||||
next: () => this.revalidate(),
|
||||
error: () => {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
revalidate = effect(
|
||||
@@ -82,5 +95,6 @@ export class WorkspacePermission extends Entity {
|
||||
|
||||
override dispose(): void {
|
||||
this.revalidate.unsubscribe();
|
||||
this.subscription?.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { WorkspaceServerService } from '../cloud/services/workspace-server';
|
||||
import { DocScope, DocService } from '../doc';
|
||||
import { NbstoreService } from '../storage';
|
||||
import {
|
||||
WorkspaceLocalState,
|
||||
WorkspaceScope,
|
||||
@@ -46,19 +47,24 @@ export function configurePermissionsModule(framework: Framework) {
|
||||
.store(WorkspacePermissionStore, [
|
||||
WorkspaceServerService,
|
||||
WorkspaceLocalState,
|
||||
NbstoreService,
|
||||
])
|
||||
.entity(WorkspacePermission, [WorkspaceService, WorkspacePermissionStore])
|
||||
.service(WorkspaceMembersService, [WorkspaceMembersStore, WorkspaceService])
|
||||
.store(WorkspaceMembersStore, [WorkspaceServerService])
|
||||
.store(WorkspaceMembersStore, [WorkspaceServerService, NbstoreService])
|
||||
.entity(WorkspaceMembers, [WorkspaceMembersStore, WorkspaceService])
|
||||
.service(MemberSearchService, [MemberSearchStore, WorkspaceService])
|
||||
.store(MemberSearchStore, [WorkspaceServerService])
|
||||
.store(MemberSearchStore, [NbstoreService])
|
||||
.service(GuardService, [
|
||||
GuardStore,
|
||||
WorkspaceService,
|
||||
WorkspacePermissionService,
|
||||
])
|
||||
.store(GuardStore, [WorkspaceService, WorkspaceServerService]);
|
||||
.store(GuardStore, [
|
||||
WorkspaceService,
|
||||
WorkspaceServerService,
|
||||
NbstoreService,
|
||||
]);
|
||||
|
||||
framework
|
||||
.scope(WorkspaceScope)
|
||||
@@ -68,5 +74,5 @@ export function configurePermissionsModule(framework: Framework) {
|
||||
WorkspaceService,
|
||||
DocService,
|
||||
])
|
||||
.store(DocGrantedUsersStore, [WorkspaceServerService]);
|
||||
.store(DocGrantedUsersStore, [WorkspaceServerService, NbstoreService]);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DocRole, type GetPageGrantedUsersListQuery } from '@affine/graphql';
|
||||
import { DocRole } from '@affine/graphql';
|
||||
import type { DocGrantedUserSnapshot } from '@affine/realtime';
|
||||
import {
|
||||
catchErrorInto,
|
||||
effect,
|
||||
@@ -9,14 +10,16 @@ import {
|
||||
Service,
|
||||
smartRetry,
|
||||
} from '@toeverything/infra';
|
||||
import type { Subscription } from 'rxjs';
|
||||
import { EMPTY, exhaustMap, tap } from 'rxjs';
|
||||
|
||||
import type { DocService } from '../../doc';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import type { DocGrantedUsersStore } from '../stores/doc-granted-users';
|
||||
|
||||
export type GrantedUser =
|
||||
GetPageGrantedUsersListQuery['workspace']['doc']['grantedUsersList']['edges'][number]['node'];
|
||||
export type GrantedUser = Omit<DocGrantedUserSnapshot, 'role'> & {
|
||||
role: DocRole;
|
||||
};
|
||||
|
||||
export class DocGrantedUsersService extends Service {
|
||||
constructor(
|
||||
@@ -25,8 +28,22 @@ export class DocGrantedUsersService extends Service {
|
||||
private readonly docService: DocService
|
||||
) {
|
||||
super();
|
||||
this.subscription = this.store
|
||||
.subscribeDocGrants(
|
||||
this.workspaceService.workspace.id,
|
||||
this.docService.doc.id
|
||||
)
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.reset();
|
||||
this.loadMore();
|
||||
},
|
||||
error: error => this.error$.setValue(error),
|
||||
});
|
||||
}
|
||||
|
||||
private readonly subscription: Subscription;
|
||||
|
||||
readonly PAGE_SIZE = 8;
|
||||
|
||||
nextCursor$ = new LiveData<string | undefined>(undefined);
|
||||
@@ -149,5 +166,6 @@ export class DocGrantedUsersService extends Service {
|
||||
|
||||
override dispose(): void {
|
||||
this.loadMore.unsubscribe();
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Service,
|
||||
smartRetry,
|
||||
} from '@toeverything/infra';
|
||||
import type { Subscription } from 'rxjs';
|
||||
import { EMPTY, exhaustMap, tap } from 'rxjs';
|
||||
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
@@ -20,8 +21,20 @@ export class MemberSearchService extends Service {
|
||||
private readonly workspaceService: WorkspaceService
|
||||
) {
|
||||
super();
|
||||
this.subscription = this.store
|
||||
.subscribeMembers(this.workspaceService.workspace.id)
|
||||
.subscribe({
|
||||
next: () => {
|
||||
if (this.searchText$.value) {
|
||||
this.search(this.searchText$.value);
|
||||
}
|
||||
},
|
||||
error: error => this.error$.setValue(error),
|
||||
});
|
||||
}
|
||||
|
||||
private readonly subscription: Subscription;
|
||||
|
||||
readonly PAGE_SIZE = 8;
|
||||
readonly searchText$ = new LiveData<string>('');
|
||||
readonly isLoading$ = new LiveData(false);
|
||||
@@ -70,4 +83,9 @@ export class MemberSearchService extends Service {
|
||||
this.searchText$.setValue(searchText ?? '');
|
||||
this.loadMore();
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this.loadMore.unsubscribe();
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import type { WorkspaceServerService } from '@affine/core/modules/cloud';
|
||||
import {
|
||||
type DocRole,
|
||||
getPageGrantedUsersListQuery,
|
||||
type GrantDocUserRolesInput,
|
||||
grantDocUserRolesMutation,
|
||||
type PaginationInput,
|
||||
@@ -12,8 +10,14 @@ import {
|
||||
} from '@affine/graphql';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { NbstoreService } from '../../storage';
|
||||
import { mapDocGrantedUserSnapshot } from './realtime-mappers';
|
||||
|
||||
export class DocGrantedUsersStore extends Store {
|
||||
constructor(private readonly workspaceServerService: WorkspaceServerService) {
|
||||
constructor(
|
||||
private readonly workspaceServerService: WorkspaceServerService,
|
||||
private readonly nbstoreService: NbstoreService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -23,20 +27,33 @@ export class DocGrantedUsersStore extends Store {
|
||||
pagination: PaginationInput,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!this.workspaceServerService.server) {
|
||||
throw new Error('No Server');
|
||||
}
|
||||
const res = await this.workspaceServerService.server.gql({
|
||||
query: getPageGrantedUsersListQuery,
|
||||
variables: {
|
||||
workspaceId,
|
||||
docId,
|
||||
pagination,
|
||||
},
|
||||
context: { signal },
|
||||
});
|
||||
return await this.nbstoreService.realtime
|
||||
.request(
|
||||
'doc.grants.get',
|
||||
{
|
||||
workspaceId,
|
||||
docId,
|
||||
pagination: {
|
||||
first: pagination.first ?? 10,
|
||||
offset: pagination.offset ?? 0,
|
||||
after: pagination.after ?? undefined,
|
||||
},
|
||||
},
|
||||
{ signal, timeoutMs: 10000 }
|
||||
)
|
||||
.then(data => ({
|
||||
...data,
|
||||
edges: data.edges.map(edge => ({
|
||||
node: mapDocGrantedUserSnapshot(edge.node),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
return res.workspace.doc.grantedUsersList;
|
||||
subscribeDocGrants(workspaceId: string, docId: string) {
|
||||
return this.nbstoreService.realtime.subscribe('doc.grants.changed', {
|
||||
workspaceId,
|
||||
docId,
|
||||
});
|
||||
}
|
||||
|
||||
async grantDocUserRoles(input: GrantDocUserRolesInput) {
|
||||
@@ -75,7 +92,7 @@ export class DocGrantedUsersStore extends Store {
|
||||
workspaceId: string,
|
||||
docId: string,
|
||||
userId: string,
|
||||
role: DocRole
|
||||
role: GrantDocUserRolesInput['role']
|
||||
) {
|
||||
if (!this.workspaceServerService.server) {
|
||||
throw new Error('No Server');
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import {
|
||||
type GetDocRolePermissionsQuery,
|
||||
getDocRolePermissionsQuery,
|
||||
type GetWorkspaceInfoQuery,
|
||||
getWorkspaceInfoQuery,
|
||||
} from '@affine/graphql';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { WorkspaceServerService } from '../../cloud';
|
||||
import type { NbstoreService } from '../../storage';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
|
||||
export type WorkspacePermissionActions = keyof Omit<
|
||||
GetWorkspaceInfoQuery['workspace']['permissions'],
|
||||
'__typename'
|
||||
>;
|
||||
export type WorkspacePermissionActions = string;
|
||||
|
||||
export type DocPermissionActions = keyof Omit<
|
||||
GetDocRolePermissionsQuery['workspace']['doc']['permissions'],
|
||||
@@ -22,7 +18,8 @@ export type DocPermissionActions = keyof Omit<
|
||||
export class GuardStore extends Store {
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly workspaceServerService: WorkspaceServerService
|
||||
private readonly workspaceServerService: WorkspaceServerService,
|
||||
private readonly nbstoreService: NbstoreService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -30,16 +27,12 @@ export class GuardStore extends Store {
|
||||
async getWorkspacePermissions(): Promise<
|
||||
Record<WorkspacePermissionActions, boolean>
|
||||
> {
|
||||
if (!this.workspaceServerService.server) {
|
||||
throw new Error('No server');
|
||||
}
|
||||
const data = await this.workspaceServerService.server.gql({
|
||||
query: getWorkspaceInfoQuery,
|
||||
variables: {
|
||||
workspaceId: this.workspaceService.workspace.id,
|
||||
},
|
||||
});
|
||||
return data.workspace.permissions;
|
||||
const { access } = await this.nbstoreService.realtime.request(
|
||||
'workspace.access.get',
|
||||
{ workspaceId: this.workspaceService.workspace.id },
|
||||
{ timeoutMs: 10000 }
|
||||
);
|
||||
return access.permissions as Record<WorkspacePermissionActions, boolean>;
|
||||
}
|
||||
|
||||
async getDocPermissions(
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { getMembersByWorkspaceIdQuery } from '@affine/graphql';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { WorkspaceServerService } from '../../cloud';
|
||||
import type { NbstoreService } from '../../storage';
|
||||
import { mapWorkspaceMemberSnapshot } from './realtime-mappers';
|
||||
|
||||
export class MemberSearchStore extends Store {
|
||||
constructor(private readonly workspaceServerService: WorkspaceServerService) {
|
||||
constructor(private readonly nbstoreService: NbstoreService) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -15,22 +15,21 @@ export class MemberSearchStore extends Store {
|
||||
take?: number,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!this.workspaceServerService.server) {
|
||||
throw new Error('No Server');
|
||||
}
|
||||
const data = await this.workspaceServerService.server.gql({
|
||||
query: getMembersByWorkspaceIdQuery,
|
||||
variables: {
|
||||
workspaceId,
|
||||
skip,
|
||||
take,
|
||||
query,
|
||||
},
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
});
|
||||
return await this.nbstoreService.realtime
|
||||
.request(
|
||||
'workspace.members.get',
|
||||
{ workspaceId, skip, take, query },
|
||||
{ signal, timeoutMs: 10000 }
|
||||
)
|
||||
.then(data => ({
|
||||
...data,
|
||||
members: data.members.map(mapWorkspaceMemberSnapshot),
|
||||
}));
|
||||
}
|
||||
|
||||
return data.workspace;
|
||||
subscribeMembers(workspaceId: string) {
|
||||
return this.nbstoreService.realtime.subscribe('workspace.members.changed', {
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
approveWorkspaceTeamMemberMutation,
|
||||
createInviteLinkMutation,
|
||||
getMembersByWorkspaceIdQuery,
|
||||
grantWorkspaceTeamMemberMutation,
|
||||
inviteByEmailsMutation,
|
||||
type Permission,
|
||||
@@ -12,9 +11,14 @@ import {
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { WorkspaceServerService } from '../../cloud';
|
||||
import type { NbstoreService } from '../../storage';
|
||||
import { mapWorkspaceMemberSnapshot } from './realtime-mappers';
|
||||
|
||||
export class WorkspaceMembersStore extends Store {
|
||||
constructor(private readonly workspaceServerService: WorkspaceServerService) {
|
||||
constructor(
|
||||
private readonly workspaceServerService: WorkspaceServerService,
|
||||
private readonly nbstoreService: NbstoreService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -24,22 +28,22 @@ export class WorkspaceMembersStore extends Store {
|
||||
take: number,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!this.workspaceServerService.server) {
|
||||
throw new Error('No Server');
|
||||
}
|
||||
const data = await this.workspaceServerService.server.gql({
|
||||
query: getMembersByWorkspaceIdQuery,
|
||||
variables: {
|
||||
workspaceId,
|
||||
skip,
|
||||
take,
|
||||
},
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
});
|
||||
return await this.nbstoreService.realtime
|
||||
.request(
|
||||
'workspace.members.get',
|
||||
{ workspaceId, skip, take },
|
||||
{ signal, timeoutMs: 10000 }
|
||||
)
|
||||
.then(data => ({
|
||||
...data,
|
||||
members: data.members.map(mapWorkspaceMemberSnapshot),
|
||||
}));
|
||||
}
|
||||
|
||||
return data.workspace;
|
||||
subscribeMembers(workspaceId: string) {
|
||||
return this.nbstoreService.realtime.subscribe('workspace.members.changed', {
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
async inviteBatch(workspaceId: string, emails: string[]) {
|
||||
|
||||
@@ -1,30 +1,32 @@
|
||||
import type { WorkspaceServerService } from '@affine/core/modules/cloud';
|
||||
import { getWorkspaceInfoQuery, leaveWorkspaceMutation } from '@affine/graphql';
|
||||
import { leaveWorkspaceMutation } from '@affine/graphql';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { NbstoreService } from '../../storage';
|
||||
import type { WorkspaceLocalState } from '../../workspace';
|
||||
|
||||
export class WorkspacePermissionStore extends Store {
|
||||
constructor(
|
||||
private readonly workspaceServerService: WorkspaceServerService,
|
||||
private readonly workspaceLocalState: WorkspaceLocalState
|
||||
private readonly workspaceLocalState: WorkspaceLocalState,
|
||||
private readonly nbstoreService: NbstoreService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async fetchWorkspaceInfo(workspaceId: string, signal?: AbortSignal) {
|
||||
if (!this.workspaceServerService.server) {
|
||||
throw new Error('No Server');
|
||||
}
|
||||
const info = await this.workspaceServerService.server.gql({
|
||||
query: getWorkspaceInfoQuery,
|
||||
variables: {
|
||||
workspaceId,
|
||||
},
|
||||
context: { signal },
|
||||
});
|
||||
const { access } = await this.nbstoreService.realtime.request(
|
||||
'workspace.access.get',
|
||||
{ workspaceId },
|
||||
{ signal, timeoutMs: 10000 }
|
||||
);
|
||||
return { workspace: access };
|
||||
}
|
||||
|
||||
return info;
|
||||
subscribeWorkspaceAccess(workspaceId: string) {
|
||||
return this.nbstoreService.realtime.subscribe('workspace.access.changed', {
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { DocRole, Permission, WorkspaceMemberStatus } from '@affine/graphql';
|
||||
import type {
|
||||
DocGrantedUserSnapshot,
|
||||
WorkspaceMemberSnapshot,
|
||||
} from '@affine/realtime';
|
||||
|
||||
import { mapRealtimeEnum } from '../../cloud/realtime/enum';
|
||||
|
||||
export function mapWorkspaceMemberSnapshot(member: WorkspaceMemberSnapshot) {
|
||||
return {
|
||||
...member,
|
||||
permission: mapRealtimeEnum(Permission, member.permission, 'permission'),
|
||||
role: mapRealtimeEnum(Permission, member.role, 'permission'),
|
||||
status: mapRealtimeEnum(
|
||||
WorkspaceMemberStatus,
|
||||
member.status,
|
||||
'workspace member status'
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function mapDocGrantedUserSnapshot(node: DocGrantedUserSnapshot) {
|
||||
return { ...node, role: mapRealtimeEnum(DocRole, node.role, 'doc role') };
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import type { WorkspaceQuotaQuery } from '@affine/graphql';
|
||||
import type { WorkspaceQuotaStateSnapshot } from '@affine/realtime';
|
||||
import { Framework } from '@toeverything/infra';
|
||||
import { Subject } from 'rxjs';
|
||||
@@ -8,8 +7,6 @@ import { describe, expect, test, vi } from 'vitest';
|
||||
import { WorkspaceQuotaStore } from '../stores/quota';
|
||||
import { WorkspaceQuota } from './quota';
|
||||
|
||||
type Quota = WorkspaceQuotaQuery['workspace']['quota'];
|
||||
|
||||
const workspaceService = {
|
||||
workspace: { id: 'workspace-1' },
|
||||
} as unknown as WorkspaceService;
|
||||
@@ -43,29 +40,6 @@ function createQuotaState(
|
||||
};
|
||||
}
|
||||
|
||||
function createQuota(overrides: Partial<Quota> = {}): Quota {
|
||||
return {
|
||||
name: 'Legacy',
|
||||
blobLimit: 1024,
|
||||
storageQuota: 2048,
|
||||
usedStorageQuota: 256,
|
||||
historyPeriod: 30 * 24 * 60 * 60 * 1000,
|
||||
memberLimit: 8,
|
||||
memberCount: 2,
|
||||
overcapacityMemberCount: 0,
|
||||
humanReadable: {
|
||||
name: 'Legacy',
|
||||
blobLimit: '1 KB',
|
||||
storageQuota: '2 KB',
|
||||
historyPeriod: '30 days',
|
||||
memberLimit: '8',
|
||||
memberCount: '2',
|
||||
overcapacityMemberCount: '0',
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function createStore(
|
||||
overrides: Partial<WorkspaceQuotaStore>,
|
||||
eventSubject = new Subject<{ type: 'ready' } | { changed: true }>()
|
||||
@@ -73,7 +47,6 @@ function createStore(
|
||||
return {
|
||||
fetchWorkspaceQuotaState: vi.fn(),
|
||||
subscribeWorkspaceQuotaState: vi.fn(() => eventSubject),
|
||||
fetchWorkspaceQuota: vi.fn(),
|
||||
...overrides,
|
||||
} as unknown as WorkspaceQuotaStore;
|
||||
}
|
||||
@@ -110,21 +83,20 @@ describe('WorkspaceQuota', () => {
|
||||
await vi.waitFor(() => expect(quota.quota$.value?.memberCount).toBe(4));
|
||||
|
||||
expect(store.fetchWorkspaceQuotaState).toHaveBeenCalledTimes(2);
|
||||
expect(store.fetchWorkspaceQuota).not.toHaveBeenCalled();
|
||||
quota.dispose();
|
||||
});
|
||||
|
||||
test('falls back to legacy GraphQL quota when realtime request fails', async () => {
|
||||
test('surfaces realtime quota errors without GraphQL fallback', async () => {
|
||||
const error = new Error('offline');
|
||||
const store = createStore({
|
||||
fetchWorkspaceQuotaState: vi.fn().mockRejectedValue(new Error('offline')),
|
||||
fetchWorkspaceQuota: vi.fn().mockResolvedValue(createQuota()),
|
||||
fetchWorkspaceQuotaState: vi.fn().mockRejectedValue(error),
|
||||
});
|
||||
const quota = createEntity(store);
|
||||
|
||||
quota.revalidate();
|
||||
|
||||
await vi.waitFor(() => expect(quota.quota$.value?.name).toBe('Legacy'));
|
||||
expect(quota.error$.value).toBeNull();
|
||||
await vi.waitFor(() => expect(quota.error$.value).toBe(error));
|
||||
expect(quota.quota$.value).toBeNull();
|
||||
quota.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import type { WorkspaceQuotaQuery } from '@affine/graphql';
|
||||
import type {
|
||||
RealtimeTopicEventOf,
|
||||
WorkspaceQuotaStateSnapshot,
|
||||
@@ -12,7 +11,25 @@ import { RealtimeLiveQuery } from '../../cloud/realtime/live-query';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import type { WorkspaceQuotaStore } from '../stores/quota';
|
||||
|
||||
type QuotaType = WorkspaceQuotaQuery['workspace']['quota'];
|
||||
type QuotaType = {
|
||||
name: string;
|
||||
blobLimit: number;
|
||||
storageQuota: number;
|
||||
usedStorageQuota: number;
|
||||
historyPeriod: number;
|
||||
memberLimit: number;
|
||||
memberCount: number;
|
||||
overcapacityMemberCount: number;
|
||||
humanReadable: {
|
||||
name: string;
|
||||
blobLimit: string;
|
||||
storageQuota: string;
|
||||
historyPeriod: string;
|
||||
memberLimit: string;
|
||||
memberCount: string;
|
||||
overcapacityMemberCount: string;
|
||||
};
|
||||
};
|
||||
|
||||
const logger = new DebugLogger('affine:workspace-permission');
|
||||
const DAY_SECONDS = 24 * 60 * 60;
|
||||
@@ -172,11 +189,6 @@ export class WorkspaceQuota extends Entity {
|
||||
signal
|
||||
)
|
||||
);
|
||||
} catch {
|
||||
return await this.store.fetchWorkspaceQuota(
|
||||
this.workspaceService.workspace.id,
|
||||
signal
|
||||
);
|
||||
} finally {
|
||||
this.isRevalidating$.setValue(false);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ export { QuotaCheck } from './views/quota-check';
|
||||
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { WorkspaceServerService } from '../cloud';
|
||||
import { NbstoreService } from '../storage';
|
||||
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
||||
import { WorkspaceQuota } from './entities/quota';
|
||||
@@ -14,6 +13,6 @@ export function configureQuotaModule(framework: Framework) {
|
||||
framework
|
||||
.scope(WorkspaceScope)
|
||||
.service(WorkspaceQuotaService)
|
||||
.store(WorkspaceQuotaStore, [WorkspaceServerService, NbstoreService])
|
||||
.store(WorkspaceQuotaStore, [NbstoreService])
|
||||
.entity(WorkspaceQuota, [WorkspaceService, WorkspaceQuotaStore]);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import type { WorkspaceServerService } from '@affine/core/modules/cloud';
|
||||
import { workspaceQuotaQuery } from '@affine/graphql';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { NbstoreService } from '../../storage';
|
||||
|
||||
export class WorkspaceQuotaStore extends Store {
|
||||
constructor(
|
||||
private readonly workspaceServerService: WorkspaceServerService,
|
||||
private readonly nbstoreService: NbstoreService
|
||||
) {
|
||||
constructor(private readonly nbstoreService: NbstoreService) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -27,20 +22,4 @@ export class WorkspaceQuotaStore extends Store {
|
||||
{ workspaceId }
|
||||
);
|
||||
}
|
||||
|
||||
async fetchWorkspaceQuota(workspaceId: string, signal?: AbortSignal) {
|
||||
if (!this.workspaceServerService.server) {
|
||||
throw new Error('No Server');
|
||||
}
|
||||
const data = await this.workspaceServerService.server.gql({
|
||||
query: workspaceQuotaQuery,
|
||||
variables: {
|
||||
id: workspaceId,
|
||||
},
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
});
|
||||
return data.workspace.quota;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import type { GetWorkspacePageByIdQuery, PublicDocMode } from '@affine/graphql';
|
||||
import {
|
||||
catchErrorInto,
|
||||
effect,
|
||||
Entity,
|
||||
fromPromise,
|
||||
LiveData,
|
||||
mapInto,
|
||||
onComplete,
|
||||
onStart,
|
||||
smartRetry,
|
||||
} from '@toeverything/infra';
|
||||
import { switchMap } from 'rxjs';
|
||||
import type { DocRole, PublicDocMode } from '@affine/graphql';
|
||||
import type {
|
||||
DocShareStateSnapshot,
|
||||
RealtimeTopicEventOf,
|
||||
} from '@affine/realtime';
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
|
||||
import { RealtimeLiveQuery } from '../../cloud/realtime/live-query';
|
||||
import type { DocService } from '../../doc';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import type { ShareStore } from '../stores/share';
|
||||
|
||||
type ShareInfoType = GetWorkspacePageByIdQuery['workspace']['doc'];
|
||||
type ShareInfoType = Omit<DocShareStateSnapshot, 'defaultRole' | 'mode'> & {
|
||||
defaultRole: DocRole;
|
||||
mode: PublicDocMode;
|
||||
};
|
||||
|
||||
export class ShareInfo extends Entity {
|
||||
info$ = new LiveData<ShareInfoType | undefined | null>(null);
|
||||
@@ -32,25 +29,30 @@ export class ShareInfo extends Entity {
|
||||
private readonly store: ShareStore
|
||||
) {
|
||||
super();
|
||||
this.liveQuery.start();
|
||||
}
|
||||
|
||||
revalidate = effect(
|
||||
switchMap(() => {
|
||||
return fromPromise(signal =>
|
||||
this.store.getShareInfoByDocId(
|
||||
this.workspaceService.workspace.id,
|
||||
this.docService.doc.id,
|
||||
signal
|
||||
)
|
||||
).pipe(
|
||||
smartRetry(),
|
||||
mapInto(this.info$),
|
||||
catchErrorInto(this.error$),
|
||||
onStart(() => this.isRevalidating$.next(true)),
|
||||
onComplete(() => this.isRevalidating$.next(false))
|
||||
);
|
||||
})
|
||||
);
|
||||
private readonly liveQuery = new RealtimeLiveQuery<
|
||||
ShareInfoType | undefined,
|
||||
RealtimeTopicEventOf<'doc.share-state.changed'>
|
||||
>({
|
||||
request: signal => this.requestShareInfo(signal),
|
||||
subscribe: () =>
|
||||
this.store.subscribeShareState(
|
||||
this.workspaceService.workspace.id,
|
||||
this.docService.doc.id
|
||||
),
|
||||
applySnapshot: info => {
|
||||
this.error$.next(null);
|
||||
this.info$.next(info);
|
||||
},
|
||||
applyEvent: () => 'revalidate',
|
||||
onError: error => this.error$.setValue(error),
|
||||
});
|
||||
|
||||
revalidate = () => {
|
||||
this.liveQuery.revalidate();
|
||||
};
|
||||
|
||||
waitForRevalidation(signal?: AbortSignal) {
|
||||
this.revalidate();
|
||||
@@ -77,4 +79,21 @@ export class ShareInfo extends Entity {
|
||||
);
|
||||
await this.waitForRevalidation();
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this.liveQuery.dispose();
|
||||
}
|
||||
|
||||
private async requestShareInfo(signal: AbortSignal) {
|
||||
this.isRevalidating$.next(true);
|
||||
try {
|
||||
return await this.store.getShareInfoByDocId(
|
||||
this.workspaceService.workspace.id,
|
||||
this.docService.doc.id,
|
||||
signal
|
||||
);
|
||||
} finally {
|
||||
this.isRevalidating$.next(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { WorkspaceServerService } from '../cloud';
|
||||
import { DocScope, DocService } from '../doc';
|
||||
import { NbstoreService } from '../storage';
|
||||
import {
|
||||
WorkspaceLocalCache,
|
||||
WorkspaceScope,
|
||||
@@ -30,5 +31,5 @@ export function configureShareDocsModule(framework: Framework) {
|
||||
.scope(DocScope)
|
||||
.service(ShareInfoService)
|
||||
.entity(ShareInfo, [WorkspaceService, DocService, ShareStore])
|
||||
.store(ShareStore, [WorkspaceServerService]);
|
||||
.store(ShareStore, [WorkspaceServerService, NbstoreService]);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import type { PublicDocMode } from '@affine/graphql';
|
||||
import {
|
||||
getWorkspacePageByIdQuery,
|
||||
DocRole,
|
||||
PublicDocMode,
|
||||
publishPageMutation,
|
||||
revokePublicPageMutation,
|
||||
} from '@affine/graphql';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { WorkspaceServerService } from '../../cloud';
|
||||
import { mapRealtimeEnum } from '../../cloud/realtime/enum';
|
||||
import type { NbstoreService } from '../../storage';
|
||||
|
||||
export class ShareStore extends Store {
|
||||
constructor(private readonly workspaceServerService: WorkspaceServerService) {
|
||||
constructor(
|
||||
private readonly workspaceServerService: WorkspaceServerService,
|
||||
private readonly nbstoreService: NbstoreService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -18,20 +23,28 @@ export class ShareStore extends Store {
|
||||
docId: string,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!this.workspaceServerService.server) {
|
||||
throw new Error('No Server');
|
||||
}
|
||||
const data = await this.workspaceServerService.server.gql({
|
||||
query: getWorkspacePageByIdQuery,
|
||||
variables: {
|
||||
pageId: docId,
|
||||
workspaceId,
|
||||
},
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
const { state } = await this.nbstoreService.realtime.request(
|
||||
'doc.share-state.get',
|
||||
{ workspaceId, docId },
|
||||
{ signal, timeoutMs: 10000 }
|
||||
);
|
||||
return state
|
||||
? {
|
||||
id: docId,
|
||||
...state,
|
||||
mode: mapRealtimeEnum(PublicDocMode, state.mode, 'public doc mode'),
|
||||
defaultRole: mapRealtimeEnum(DocRole, state.defaultRole, 'doc role'),
|
||||
title: null,
|
||||
summary: null,
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
|
||||
subscribeShareState(workspaceId: string, docId: string) {
|
||||
return this.nbstoreService.realtime.subscribe('doc.share-state.changed', {
|
||||
workspaceId,
|
||||
docId,
|
||||
});
|
||||
return data.workspace.doc ?? undefined;
|
||||
}
|
||||
|
||||
async enableSharePage(
|
||||
|
||||
@@ -1,90 +1,91 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import type { GetWorkspaceConfigQuery, InviteLink } from '@affine/graphql';
|
||||
import {
|
||||
catchErrorInto,
|
||||
effect,
|
||||
Entity,
|
||||
fromPromise,
|
||||
LiveData,
|
||||
onComplete,
|
||||
onStart,
|
||||
smartRetry,
|
||||
} from '@toeverything/infra';
|
||||
import { exhaustMap, tap } from 'rxjs';
|
||||
import type {
|
||||
RealtimeTopicEventOf,
|
||||
WorkspaceConfigSnapshot,
|
||||
WorkspaceInviteLinkSnapshot,
|
||||
} from '@affine/realtime';
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
|
||||
import { RealtimeLiveQuery } from '../../cloud/realtime/live-query';
|
||||
import type { WorkspaceService } from '../../workspace';
|
||||
import type { WorkspaceShareSettingStore } from '../stores/share-setting';
|
||||
|
||||
type EnableAi = GetWorkspaceConfigQuery['workspace']['enableAi'];
|
||||
type EnableSharing = GetWorkspaceConfigQuery['workspace']['enableSharing'];
|
||||
type EnableUrlPreview =
|
||||
GetWorkspaceConfigQuery['workspace']['enableUrlPreview'];
|
||||
|
||||
const logger = new DebugLogger('affine:workspace-permission');
|
||||
type InviteLink = WorkspaceInviteLinkSnapshot;
|
||||
|
||||
export class WorkspaceShareSetting extends Entity {
|
||||
enableAi$ = new LiveData<EnableAi | null>(null);
|
||||
enableSharing$ = new LiveData<EnableSharing | null>(null);
|
||||
enableUrlPreview$ = new LiveData<EnableUrlPreview | null>(null);
|
||||
enableAi$ = new LiveData<boolean | null>(null);
|
||||
enableSharing$ = new LiveData<boolean | null>(null);
|
||||
enableUrlPreview$ = new LiveData<boolean | null>(null);
|
||||
inviteLink$ = new LiveData<InviteLink | null>(null);
|
||||
isLoading$ = new LiveData(false);
|
||||
error$ = new LiveData<any>(null);
|
||||
private inviteLinkStarted = false;
|
||||
private inviteLinkExpireTimer?: ReturnType<typeof setTimeout>;
|
||||
private readonly configLiveQuery = new RealtimeLiveQuery<
|
||||
WorkspaceConfigSnapshot,
|
||||
RealtimeTopicEventOf<'workspace.config.changed'>
|
||||
>({
|
||||
request: signal => this.requestWorkspaceConfig(signal),
|
||||
subscribe: () =>
|
||||
this.store.subscribeWorkspaceConfig(this.workspaceService.workspace.id),
|
||||
applySnapshot: value => {
|
||||
this.error$.next(null);
|
||||
this.enableAi$.next(value.enableAi);
|
||||
this.enableSharing$.next(value.enableSharing);
|
||||
this.enableUrlPreview$.next(value.enableUrlPreview);
|
||||
},
|
||||
applyEvent: () => 'revalidate',
|
||||
onError: error => {
|
||||
logger.error('Failed to fetch workspace share settings', error);
|
||||
this.error$.setValue(error);
|
||||
},
|
||||
});
|
||||
private readonly inviteLinkLiveQuery = new RealtimeLiveQuery<
|
||||
InviteLink | null,
|
||||
RealtimeTopicEventOf<'workspace.invite-link.changed'>
|
||||
>({
|
||||
request: signal =>
|
||||
this.store.fetchInviteLink(this.workspaceService.workspace.id, signal),
|
||||
subscribe: () =>
|
||||
this.store.subscribeInviteLink(this.workspaceService.workspace.id),
|
||||
applySnapshot: value => {
|
||||
this.error$.next(null);
|
||||
this.inviteLink$.next(value);
|
||||
this.scheduleInviteLinkExpiry(value);
|
||||
},
|
||||
applyEvent: () => 'revalidate',
|
||||
onError: error => {
|
||||
logger.error('Failed to fetch workspace invite link', error);
|
||||
this.error$.setValue(error);
|
||||
this.inviteLinkLiveQuery.stop();
|
||||
this.inviteLinkStarted = false;
|
||||
},
|
||||
});
|
||||
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly store: WorkspaceShareSettingStore
|
||||
) {
|
||||
super();
|
||||
this.revalidate();
|
||||
this.configLiveQuery.start();
|
||||
}
|
||||
|
||||
revalidate = effect(
|
||||
exhaustMap(() => {
|
||||
return fromPromise(signal =>
|
||||
this.store.fetchWorkspaceConfig(
|
||||
this.workspaceService.workspace.id,
|
||||
signal
|
||||
)
|
||||
).pipe(
|
||||
smartRetry(),
|
||||
tap(value => {
|
||||
if (value) {
|
||||
this.enableAi$.next(value.enableAi);
|
||||
this.enableSharing$.next(value.enableSharing);
|
||||
this.enableUrlPreview$.next(value.enableUrlPreview);
|
||||
}
|
||||
}),
|
||||
catchErrorInto(this.error$, error => {
|
||||
logger.error('Failed to fetch workspace share settings', error);
|
||||
}),
|
||||
onStart(() => this.isLoading$.setValue(true)),
|
||||
onComplete(() => this.isLoading$.setValue(false))
|
||||
);
|
||||
})
|
||||
);
|
||||
revalidate = () => {
|
||||
this.configLiveQuery.revalidate();
|
||||
};
|
||||
|
||||
revalidateInviteLink = effect(
|
||||
exhaustMap(() => {
|
||||
return fromPromise(signal =>
|
||||
this.store.fetchInviteLink(this.workspaceService.workspace.id, signal)
|
||||
).pipe(
|
||||
smartRetry(),
|
||||
tap(value => {
|
||||
this.inviteLink$.next(value);
|
||||
}),
|
||||
catchErrorInto(this.error$, error => {
|
||||
logger.error('Failed to fetch workspace invite link', error);
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
revalidateInviteLink = () => {
|
||||
this.ensureInviteLinkStarted();
|
||||
this.inviteLinkLiveQuery.revalidate();
|
||||
};
|
||||
|
||||
async waitForRevalidation(signal?: AbortSignal) {
|
||||
this.revalidate();
|
||||
await this.isLoading$.waitFor(isLoading => !isLoading, signal);
|
||||
}
|
||||
|
||||
async setEnableUrlPreview(enableUrlPreview: EnableUrlPreview) {
|
||||
async setEnableUrlPreview(enableUrlPreview: boolean) {
|
||||
await this.store.updateWorkspaceEnableUrlPreview(
|
||||
this.workspaceService.workspace.id,
|
||||
enableUrlPreview
|
||||
@@ -92,7 +93,7 @@ export class WorkspaceShareSetting extends Entity {
|
||||
await this.waitForRevalidation();
|
||||
}
|
||||
|
||||
async setEnableSharing(enableSharing: EnableSharing) {
|
||||
async setEnableSharing(enableSharing: boolean) {
|
||||
await this.store.updateWorkspaceEnableSharing(
|
||||
this.workspaceService.workspace.id,
|
||||
enableSharing
|
||||
@@ -100,7 +101,7 @@ export class WorkspaceShareSetting extends Entity {
|
||||
await this.waitForRevalidation();
|
||||
}
|
||||
|
||||
async setEnableAi(enableAi: EnableAi) {
|
||||
async setEnableAi(enableAi: boolean) {
|
||||
await this.store.updateWorkspaceEnableAi(
|
||||
this.workspaceService.workspace.id,
|
||||
enableAi
|
||||
@@ -109,7 +110,55 @@ export class WorkspaceShareSetting extends Entity {
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this.revalidate.unsubscribe();
|
||||
this.revalidateInviteLink.unsubscribe();
|
||||
this.configLiveQuery.dispose();
|
||||
this.inviteLinkLiveQuery.dispose();
|
||||
this.clearInviteLinkExpireTimer();
|
||||
}
|
||||
|
||||
private ensureInviteLinkStarted() {
|
||||
if (this.inviteLinkStarted) {
|
||||
return;
|
||||
}
|
||||
this.inviteLinkStarted = true;
|
||||
this.inviteLinkLiveQuery.start();
|
||||
}
|
||||
|
||||
private scheduleInviteLinkExpiry(inviteLink: InviteLink | null) {
|
||||
this.clearInviteLinkExpireTimer();
|
||||
if (!inviteLink) {
|
||||
return;
|
||||
}
|
||||
const expireAt = new Date(inviteLink.expireTime).getTime();
|
||||
if (!Number.isFinite(expireAt)) {
|
||||
return;
|
||||
}
|
||||
const delay = expireAt - Date.now();
|
||||
if (delay <= 0) {
|
||||
this.inviteLink$.next(null);
|
||||
return;
|
||||
}
|
||||
this.inviteLinkExpireTimer = setTimeout(() => {
|
||||
this.inviteLink$.next(null);
|
||||
}, delay);
|
||||
this.inviteLinkExpireTimer.unref?.();
|
||||
}
|
||||
|
||||
private clearInviteLinkExpireTimer() {
|
||||
if (this.inviteLinkExpireTimer) {
|
||||
clearTimeout(this.inviteLinkExpireTimer);
|
||||
this.inviteLinkExpireTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private async requestWorkspaceConfig(signal: AbortSignal) {
|
||||
this.isLoading$.setValue(true);
|
||||
try {
|
||||
return await this.store.fetchWorkspaceConfig(
|
||||
this.workspaceService.workspace.id,
|
||||
signal
|
||||
);
|
||||
} finally {
|
||||
this.isLoading$.setValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ export { WorkspaceShareSettingService } from './services/share-setting';
|
||||
import { type Framework } from '@toeverything/infra';
|
||||
|
||||
import { WorkspaceServerService } from '../cloud';
|
||||
import { NbstoreService } from '../storage';
|
||||
import { WorkspaceScope, WorkspaceService } from '../workspace';
|
||||
import { WorkspaceShareSetting } from './entities/share-setting';
|
||||
import { WorkspaceShareSettingService } from './services/share-setting';
|
||||
@@ -12,7 +13,7 @@ export function configureShareSettingModule(framework: Framework) {
|
||||
framework
|
||||
.scope(WorkspaceScope)
|
||||
.service(WorkspaceShareSettingService)
|
||||
.store(WorkspaceShareSettingStore, [WorkspaceServerService])
|
||||
.store(WorkspaceShareSettingStore, [WorkspaceServerService, NbstoreService])
|
||||
.entity(WorkspaceShareSetting, [
|
||||
WorkspaceService,
|
||||
WorkspaceShareSettingStore,
|
||||
|
||||
@@ -1,48 +1,50 @@
|
||||
import type { WorkspaceServerService } from '@affine/core/modules/cloud';
|
||||
import {
|
||||
getWorkspaceConfigQuery,
|
||||
getWorkspaceInviteLinkQuery,
|
||||
setEnableAiMutation,
|
||||
setEnableSharingMutation,
|
||||
setEnableUrlPreviewMutation,
|
||||
} from '@affine/graphql';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { NbstoreService } from '../../storage';
|
||||
|
||||
export class WorkspaceShareSettingStore extends Store {
|
||||
constructor(private readonly workspaceServerService: WorkspaceServerService) {
|
||||
constructor(
|
||||
private readonly workspaceServerService: WorkspaceServerService,
|
||||
private readonly nbstoreService: NbstoreService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async fetchWorkspaceConfig(workspaceId: string, signal?: AbortSignal) {
|
||||
if (!this.workspaceServerService.server) {
|
||||
throw new Error('No Server');
|
||||
}
|
||||
const data = await this.workspaceServerService.server.gql({
|
||||
query: getWorkspaceConfigQuery,
|
||||
variables: {
|
||||
id: workspaceId,
|
||||
},
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
const { config } = await this.nbstoreService.realtime.request(
|
||||
'workspace.config.get',
|
||||
{ workspaceId },
|
||||
{ signal, timeoutMs: 10000 }
|
||||
);
|
||||
return config;
|
||||
}
|
||||
|
||||
subscribeWorkspaceConfig(workspaceId: string) {
|
||||
return this.nbstoreService.realtime.subscribe('workspace.config.changed', {
|
||||
workspaceId,
|
||||
});
|
||||
return data.workspace;
|
||||
}
|
||||
|
||||
async fetchInviteLink(workspaceId: string, signal?: AbortSignal) {
|
||||
if (!this.workspaceServerService.server) {
|
||||
throw new Error('No Server');
|
||||
}
|
||||
const data = await this.workspaceServerService.server.gql({
|
||||
query: getWorkspaceInviteLinkQuery,
|
||||
variables: {
|
||||
id: workspaceId,
|
||||
},
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
});
|
||||
return data.workspace.inviteLink;
|
||||
const { inviteLink } = await this.nbstoreService.realtime.request(
|
||||
'workspace.invite-link.get',
|
||||
{ workspaceId },
|
||||
{ signal, timeoutMs: 10000 }
|
||||
);
|
||||
return inviteLink;
|
||||
}
|
||||
|
||||
subscribeInviteLink(workspaceId: string) {
|
||||
return this.nbstoreService.realtime.subscribe(
|
||||
'workspace.invite-link.changed',
|
||||
{ workspaceId }
|
||||
);
|
||||
}
|
||||
|
||||
async updateWorkspaceEnableAi(
|
||||
|
||||
@@ -3,7 +3,6 @@ import { DebugLogger } from '@affine/debug';
|
||||
import {
|
||||
createWorkspaceMutation,
|
||||
deleteWorkspaceMutation,
|
||||
getWorkspaceInfoQuery,
|
||||
getWorkspacesQuery,
|
||||
ServerDeploymentType,
|
||||
ServerFeature,
|
||||
@@ -67,7 +66,7 @@ import {
|
||||
GraphQLService,
|
||||
WorkspaceServerService,
|
||||
} from '../../cloud';
|
||||
import type { GlobalState } from '../../storage';
|
||||
import { type GlobalState, NbstoreService } from '../../storage';
|
||||
import type {
|
||||
Workspace,
|
||||
WorkspaceFlavourProvider,
|
||||
@@ -90,6 +89,7 @@ const logger = new DebugLogger('affine:cloud-workspace-flavour-provider');
|
||||
class CloudWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
|
||||
private readonly authService: AuthService;
|
||||
private readonly graphqlService: GraphQLService;
|
||||
private readonly nbstoreService: NbstoreService;
|
||||
private readonly unsubscribeAccountChanged: () => void;
|
||||
|
||||
constructor(
|
||||
@@ -98,6 +98,7 @@ class CloudWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
|
||||
) {
|
||||
this.authService = server.scope.get(AuthService);
|
||||
this.graphqlService = server.scope.get(GraphQLService);
|
||||
this.nbstoreService = server.scope.get(NbstoreService);
|
||||
this.unsubscribeAccountChanged = this.server.scope.eventBus.on(
|
||||
AccountChanged,
|
||||
() => {
|
||||
@@ -444,13 +445,12 @@ class CloudWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
|
||||
}
|
||||
|
||||
private async getWorkspaceInfo(workspaceId: string, signal?: AbortSignal) {
|
||||
return await this.graphqlService.gql({
|
||||
query: getWorkspaceInfoQuery,
|
||||
variables: {
|
||||
workspaceId,
|
||||
},
|
||||
context: { signal },
|
||||
});
|
||||
const { access } = await this.nbstoreService.realtime.request(
|
||||
'workspace.access.get',
|
||||
{ workspaceId },
|
||||
{ signal, timeoutMs: 10000 }
|
||||
);
|
||||
return { workspace: access };
|
||||
}
|
||||
|
||||
getEngineWorkerInitOptions(workspaceId: string): WorkerInitOptions {
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
addWorkspaceEmbeddingFilesMutation,
|
||||
addWorkspaceEmbeddingIgnoredDocsMutation,
|
||||
getAllWorkspaceEmbeddingIgnoredDocsQuery,
|
||||
getWorkspaceConfigQuery,
|
||||
getWorkspaceEmbeddingFilesQuery,
|
||||
type PaginationInput,
|
||||
removeWorkspaceEmbeddingFilesMutation,
|
||||
@@ -22,19 +21,12 @@ export class EmbeddingStore extends Store {
|
||||
}
|
||||
|
||||
async getEnabled(workspaceId: string, signal?: AbortSignal) {
|
||||
if (!this.workspaceServerService.server) {
|
||||
throw new Error('No Server');
|
||||
}
|
||||
const data = await this.workspaceServerService.server.gql({
|
||||
query: getWorkspaceConfigQuery,
|
||||
variables: {
|
||||
id: workspaceId,
|
||||
},
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
});
|
||||
return data.workspace.enableDocEmbedding;
|
||||
const { config } = await this.nbstoreService.realtime.request(
|
||||
'workspace.config.get',
|
||||
{ workspaceId },
|
||||
{ signal, timeoutMs: 10000 }
|
||||
);
|
||||
return config.enableDocEmbedding;
|
||||
}
|
||||
|
||||
async updateEnabled(
|
||||
|
||||
Reference in New Issue
Block a user