Skip to content

Commit dd2dbed

Browse files
authored
fix: permission control doesn't work when opening the page at the first time (#8023)
* fix: permission control doesn't work when opening the page at the first time * feat: enable tracing events cost time * feat: leave shared page via ... menu * fix: hide the create page button if user is guest * feat: disable publish button if user is a guest * feat: add shared user table * feat: integrate sql into folder manager * feat: integrate shared user notification * fix: flutter analyze * fix: revert local shared user data * fix: disable share button on mobile if user is a guest * feat: save the shared user data * feat: integrate pro plan workflow into share menu * chore: format code * feat: disable share menu in local mode * chore: format code
1 parent 11200a5 commit dd2dbed

File tree

69 files changed

+1219
-226
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+1219
-226
lines changed

frontend/appflowy_flutter/lib/core/config/kv_keys.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,9 @@ class KVKeys {
120120
///
121121
/// The value is a json list of id
122122
static const String compactModeIds = 'compactModeIds';
123+
124+
/// v0.9.4: has the user clicked the upgrade to pro button
125+
/// The value is a boolean string
126+
static const String hasClickedUpgradeToProButton =
127+
'hasClickedUpgradeToProButton';
123128
}

frontend/appflowy_flutter/lib/features/page_access_level/data/repositories/page_access_level_repository.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:appflowy/features/share_tab/data/models/models.dart';
22
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
33
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
4+
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
45
import 'package:appflowy_result/appflowy_result.dart';
56

67
/// Abstract repository for managing view lock status.
@@ -26,4 +27,7 @@ abstract class PageAccessLevelRepository {
2627
Future<FlowyResult<SharedSectionType, FlowyError>> getSectionType(
2728
String pageId,
2829
);
30+
31+
/// Get current workspace
32+
Future<FlowyResult<UserWorkspacePB, FlowyError>> getCurrentWorkspace();
2933
}

frontend/appflowy_flutter/lib/features/page_access_level/data/repositories/rust_page_access_level_repository_impl.dart

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import 'package:appflowy_backend/dispatch/dispatch.dart';
77
import 'package:appflowy_backend/log.dart';
88
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
99
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
10-
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
11-
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pbenum.dart';
10+
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'
11+
hide AFRolePB;
12+
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
1213
import 'package:appflowy_result/appflowy_result.dart';
1314
import 'package:collection/collection.dart';
1415

@@ -18,7 +19,7 @@ class RustPageAccessLevelRepositoryImpl implements PageAccessLevelRepository {
1819
final result = await ViewBackendService.getView(pageId);
1920
return result.fold(
2021
(view) {
21-
Log.info('get view success: ${view.id}');
22+
Log.debug('get view(${view.id}) success');
2223
return FlowyResult.success(view);
2324
},
2425
(error) {
@@ -33,7 +34,7 @@ class RustPageAccessLevelRepositoryImpl implements PageAccessLevelRepository {
3334
final result = await ViewBackendService.lockView(pageId);
3435
return result.fold(
3536
(_) {
36-
Log.info('lock view success: $pageId');
37+
Log.debug('lock view($pageId) success');
3738
return FlowyResult.success(null);
3839
},
3940
(error) {
@@ -48,7 +49,7 @@ class RustPageAccessLevelRepositoryImpl implements PageAccessLevelRepository {
4849
final result = await ViewBackendService.unlockView(pageId);
4950
return result.fold(
5051
(_) {
51-
Log.info('unlock view success: $pageId');
52+
Log.debug('unlock view($pageId) success');
5253
return FlowyResult.success(null);
5354
},
5455
(error) {
@@ -58,6 +59,11 @@ class RustPageAccessLevelRepositoryImpl implements PageAccessLevelRepository {
5859
);
5960
}
6061

62+
/// 1. local users have full access
63+
/// 2. local workspace users have full access
64+
/// 3. page creator has full access
65+
/// 4. owner and members in public page have full access
66+
/// 5. check the shared users list
6167
@override
6268
Future<FlowyResult<ShareAccessLevel, FlowyError>> getAccessLevel(
6369
String pageId,
@@ -67,6 +73,7 @@ class RustPageAccessLevelRepositoryImpl implements PageAccessLevelRepository {
6773
(s) => s,
6874
(_) => null,
6975
);
76+
7077
if (user == null) {
7178
return FlowyResult.failure(
7279
FlowyError(
@@ -86,6 +93,41 @@ class RustPageAccessLevelRepositoryImpl implements PageAccessLevelRepository {
8693
return FlowyResult.success(ShareAccessLevel.fullAccess);
8794
}
8895

96+
// If the user is the creator of the page, they can always have full access.
97+
final viewResult = await getView(pageId);
98+
final view = viewResult.fold(
99+
(s) => s,
100+
(_) => null,
101+
);
102+
if (view?.createdBy == user.id) {
103+
return FlowyResult.success(ShareAccessLevel.fullAccess);
104+
}
105+
106+
// If the page is public, the user can always have full access.
107+
final workspaceResult = await getCurrentWorkspace();
108+
final workspace = workspaceResult.fold(
109+
(s) => s,
110+
(_) => null,
111+
);
112+
if (workspace == null) {
113+
return FlowyResult.failure(
114+
FlowyError(
115+
code: ErrorCode.Internal,
116+
msg: 'Current workspace not found',
117+
),
118+
);
119+
}
120+
121+
final sectionTypeResult = await getSectionType(pageId);
122+
final sectionType = sectionTypeResult.fold(
123+
(s) => s,
124+
(_) => null,
125+
);
126+
if (sectionType == SharedSectionType.public &&
127+
workspace.role != AFRolePB.Guest) {
128+
return FlowyResult.success(ShareAccessLevel.fullAccess);
129+
}
130+
89131
final email = user.email;
90132

91133
final request = GetSharedUsersPayloadPB(
@@ -100,7 +142,7 @@ class RustPageAccessLevelRepositoryImpl implements PageAccessLevelRepository {
100142
)
101143
?.accessLevel
102144
.shareAccessLevel ??
103-
ShareAccessLevel.readAndWrite;
145+
ShareAccessLevel.readOnly;
104146

105147
Log.debug('current user access level: $accessLevel, in page: $pageId');
106148

@@ -111,8 +153,8 @@ class RustPageAccessLevelRepositoryImpl implements PageAccessLevelRepository {
111153
'failed to get user access level: $failure, in page: $pageId',
112154
);
113155

114-
// return the read and write access level if the user is not found
115-
return FlowyResult.success(ShareAccessLevel.readAndWrite);
156+
// return the read access level if the user is not found
157+
return FlowyResult.success(ShareAccessLevel.readOnly);
116158
},
117159
);
118160
}
@@ -138,4 +180,27 @@ class RustPageAccessLevelRepositoryImpl implements PageAccessLevelRepository {
138180
},
139181
);
140182
}
183+
184+
@override
185+
Future<FlowyResult<UserWorkspacePB, FlowyError>> getCurrentWorkspace() async {
186+
final result = await UserBackendService.getCurrentWorkspace();
187+
final currentWorkspaceId = result.fold(
188+
(s) => s.id,
189+
(_) => null,
190+
);
191+
192+
if (currentWorkspaceId == null) {
193+
return FlowyResult.failure(
194+
FlowyError(
195+
code: ErrorCode.Internal,
196+
msg: 'Current workspace not found',
197+
),
198+
);
199+
}
200+
201+
final workspaceResult = await UserBackendService.getWorkspaceById(
202+
currentWorkspaceId,
203+
);
204+
return workspaceResult;
205+
}
141206
}

frontend/appflowy_flutter/lib/features/page_access_level/logic/page_access_level_state.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ class PageAccessLevelState {
88
isLocked: false,
99
lockCounter: 0,
1010
sectionType: SharedSectionType.public,
11-
accessLevel: ShareAccessLevel
12-
.readAndWrite, // replace it with readOnly if we support offline.
11+
accessLevel: ShareAccessLevel.readOnly,
1312
);
1413

1514
const PageAccessLevelState({

frontend/appflowy_flutter/lib/features/share_tab/data/repositories/local_share_with_user_repository_impl.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,4 +181,18 @@ class LocalShareWithUserRepositoryImpl extends ShareWithUserRepository {
181181
}) async {
182182
return FlowySuccess(SharedSectionType.private);
183183
}
184+
185+
@override
186+
Future<bool> getUpgradeToProButtonClicked({
187+
required String workspaceId,
188+
}) async {
189+
return false;
190+
}
191+
192+
@override
193+
Future<void> setUpgradeToProButtonClicked({
194+
required String workspaceId,
195+
}) async {
196+
return;
197+
}
184198
}

frontend/appflowy_flutter/lib/features/share_tab/data/repositories/rust_share_with_user_repository_impl.dart

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import 'package:appflowy/core/config/kv.dart';
2+
import 'package:appflowy/core/config/kv_keys.dart';
13
import 'package:appflowy/features/share_tab/data/models/models.dart';
24
import 'package:appflowy/features/util/extensions.dart';
5+
import 'package:appflowy/startup/startup.dart';
36
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
47
import 'package:appflowy/workspace/application/view/view_ext.dart';
58
import 'package:appflowy_backend/dispatch/dispatch.dart';
@@ -52,12 +55,12 @@ class RustShareWithUserRepositoryImpl extends ShareWithUserRepository {
5255

5356
return result.fold(
5457
(success) {
55-
Log.info('remove users($emails) from shared page($pageId)');
58+
Log.debug('remove users($emails) from shared page($pageId)');
5659

5760
return FlowySuccess(success);
5861
},
5962
(failure) {
60-
Log.error('removeUserFromPage: $failure');
63+
Log.error('remove users($emails) from shared page($pageId): $failure');
6164

6265
return FlowyFailure(failure);
6366
},
@@ -74,13 +77,13 @@ class RustShareWithUserRepositoryImpl extends ShareWithUserRepository {
7477
viewId: pageId,
7578
emails: emails,
7679
accessLevel: accessLevel.accessLevel,
77-
autoConfirm: true, // TODO: remove this after the backend is ready
80+
autoConfirm: true,
7881
);
7982
final result = await FolderEventSharePageWithUser(request).send();
8083

8184
return result.fold(
8285
(success) {
83-
Log.info(
86+
Log.debug(
8487
'share page($pageId) with users($emails) with access level($accessLevel)',
8588
);
8689

@@ -117,7 +120,7 @@ class RustShareWithUserRepositoryImpl extends ShareWithUserRepository {
117120
final result = await UserEventUpdateWorkspaceMember(request).send();
118121
return result.fold(
119122
(success) {
120-
Log.info(
123+
Log.debug(
121124
'change role($role) for user($email) in workspaceId($workspaceId)',
122125
);
123126
return FlowySuccess(success);
@@ -161,4 +164,28 @@ class RustShareWithUserRepositoryImpl extends ShareWithUserRepository {
161164

162165
return FlowySuccess(sectionType);
163166
}
167+
168+
@override
169+
Future<bool> getUpgradeToProButtonClicked({
170+
required String workspaceId,
171+
}) async {
172+
final result = await getIt<KeyValueStorage>().getWithFormat(
173+
'${KVKeys.hasClickedUpgradeToProButton}_$workspaceId',
174+
(value) => bool.parse(value),
175+
);
176+
if (result == null) {
177+
return false;
178+
}
179+
return result;
180+
}
181+
182+
@override
183+
Future<void> setUpgradeToProButtonClicked({
184+
required String workspaceId,
185+
}) async {
186+
await getIt<KeyValueStorage>().set(
187+
'${KVKeys.hasClickedUpgradeToProButton}_$workspaceId',
188+
'true',
189+
);
190+
}
164191
}

frontend/appflowy_flutter/lib/features/share_tab/data/repositories/share_with_user_repository.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,14 @@ abstract class ShareWithUserRepository {
4747
Future<FlowyResult<SharedSectionType, FlowyError>> getCurrentPageSectionType({
4848
required String pageId,
4949
});
50+
51+
/// Get the upgrade to pro button has been clicked.
52+
Future<bool> getUpgradeToProButtonClicked({
53+
required String workspaceId,
54+
});
55+
56+
/// Set the upgrade to pro button has been clicked.
57+
Future<void> setUpgradeToProButtonClicked({
58+
required String workspaceId,
59+
});
5060
}

0 commit comments

Comments
 (0)