Skip to content

Commit 1a7d1e0

Browse files
committed
feat(shared): add content limitation service
- Implement ContentLimitationService to centralize content limitation logic - Define ContentAction and LimitationStatus enums for better readability - Add checkAction method to determine if a user can perform a content-related action - Implement role-based limitation handling - Ensure service fails open in case of incomplete data to prevent unnecessary blocking
1 parent 84b23c6 commit 1a7d1e0

File tree

1 file changed

+118
-0
lines changed

1 file changed

+118
-0
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import 'package:core/core.dart';
2+
import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart';
3+
4+
5+
/// Defines the specific type of content-related action a user is trying to
6+
/// perform, which may be subject to limitations.
7+
enum ContentAction {
8+
/// The action of bookmarking a headline.
9+
bookmarkHeadline,
10+
11+
/// The action of following a topic.
12+
followTopic,
13+
14+
/// The action of following a source.
15+
followSource,
16+
17+
/// The action of following a country.
18+
followCountry,
19+
}
20+
21+
/// Defines the outcome of a content limitation check.
22+
enum LimitationStatus {
23+
/// The user is permitted to perform the action.
24+
allowed,
25+
26+
/// The user has reached the content limit for anonymous (guest) users.
27+
anonymousLimitReached,
28+
29+
/// The user has reached the content limit for standard (free) users.
30+
standardUserLimitReached,
31+
32+
/// The user has reached the content limit for premium users.
33+
premiumUserLimitReached,
34+
}
35+
36+
37+
/// {@template content_limitation_service}
38+
/// A service that centralizes the logic for checking if a user can perform
39+
/// a content-related action based on their role and remote configuration limits.
40+
///
41+
/// This service acts as the single source of truth for content limitations,
42+
/// ensuring that rules for actions like bookmarking or following are applied
43+
/// consistently throughout the application.
44+
/// {@endtemplate}
45+
class ContentLimitationService {
46+
/// {@macro content_limitation_service}
47+
const ContentLimitationService({required AppBloc appBloc})
48+
: _appBloc = appBloc;
49+
50+
final AppBloc _appBloc;
51+
52+
/// Checks if the current user is allowed to perform a given [action].
53+
///
54+
/// Returns a [LimitationStatus] indicating whether the action is allowed or
55+
/// if a specific limit has been reached.
56+
LimitationStatus checkAction(ContentAction action) {
57+
final state = _appBloc.state;
58+
final user = state.user;
59+
final preferences = state.userContentPreferences;
60+
final remoteConfig = state.remoteConfig;
61+
62+
// Fail open: If essential data is missing, allow the action to prevent
63+
// blocking users due to an incomplete app state.
64+
if (user == null || preferences == null || remoteConfig == null) {
65+
return LimitationStatus.allowed;
66+
}
67+
68+
final limits = remoteConfig.contentConfiguration;
69+
final role = user.appRole;
70+
71+
switch (action) {
72+
case ContentAction.bookmarkHeadline:
73+
final count = preferences.savedHeadlines.length;
74+
final limit = limits.savedHeadlinesLimit[role];
75+
if (limit != null && count >= limit) {
76+
return _getLimitationStatusForRole(role);
77+
}
78+
79+
case ContentAction.followTopic:
80+
final count = preferences.followedTopics.length;
81+
final limit = limits.followedTopicsLimit[role];
82+
if (limit != null && count >= limit) {
83+
return _getLimitationStatusForRole(role);
84+
}
85+
86+
case ContentAction.followSource:
87+
final count = preferences.followedSources.length;
88+
final limit = limits.followedSourcesLimit[role];
89+
if (limit != null && count >= limit) {
90+
return _getLimitationStatusForRole(role);
91+
}
92+
93+
case ContentAction.followCountry:
94+
final count = preferences.followedCountries.length;
95+
final limit = limits.followedCountriesLimit[role];
96+
if (limit != null && count >= limit) {
97+
return _getLimitationStatusForRole(role);
98+
}
99+
}
100+
101+
// If no limit was hit, the action is allowed.
102+
return LimitationStatus.allowed;
103+
}
104+
105+
/// Maps an [AppUserRole] to the corresponding [LimitationStatus].
106+
///
107+
/// This helper function ensures a consistent mapping when a limit is reached.
108+
LimitationStatus _getLimitationStatusForRole(AppUserRole role) {
109+
switch (role) {
110+
case AppUserRole.guestUser:
111+
return LimitationStatus.anonymousLimitReached;
112+
case AppUserRole.standardUser:
113+
return LimitationStatus.standardUserLimitReached;
114+
case AppUserRole.premiumUser:
115+
return LimitationStatus.premiumUserLimitReached;
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)