Skip to content

Commit 0378a35

Browse files
committed
refactor(limits): adapt user preference limits to new config structure
Updates `DefaultUserPreferenceLimitService` to enforce limits using the new map-based `UserPreferenceConfig` from `RemoteConfig`. This includes validating `followedItemsLimit`, `savedHeadlinesLimit`, `savedHeadlineFiltersLimit`, and `savedSourceFiltersLimit` against user roles. Removes reliance on the deprecated `interestConfig` and individual limit properties.
1 parent e08b1f4 commit 0378a35

File tree

1 file changed

+126
-68
lines changed

1 file changed

+126
-68
lines changed

lib/src/services/default_user_preference_limit_service.dart

Lines changed: 126 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,13 @@ class DefaultUserPreferenceLimitService implements UserPreferenceLimitService {
3535
);
3636
final limits = remoteConfig.userPreferenceConfig;
3737

38-
final (followedItemsLimit, savedHeadlinesLimit) = _getLimitsForRole(
38+
// Retrieve all relevant limits for the user's role from the remote configuration.
39+
final (
40+
followedItemsLimit,
41+
savedHeadlinesLimit,
42+
savedHeadlineFiltersLimit,
43+
savedSourceFiltersLimit,
44+
) = _getLimitsForRole(
3945
user.appRole,
4046
limits,
4147
);
@@ -85,98 +91,150 @@ class DefaultUserPreferenceLimitService implements UserPreferenceLimitService {
8591
);
8692
}
8793

88-
// --- 2. Check interest-specific limits ---
89-
final interestLimits = remoteConfig.interestConfig.limits[user.appRole];
90-
if (interestLimits == null) {
94+
// --- 2. Check saved headline filter limits ---
95+
// Validate the total number of saved headline filters.
96+
if (updatedPreferences.savedHeadlineFilters.length >
97+
savedHeadlineFiltersLimit.total) {
9198
_log.severe(
92-
'Interest limits not found for role ${user.appRole}. '
93-
'Denying request by default.',
99+
'User ${user.id} exceeded total saved headline filter limit: '
100+
'${savedHeadlineFiltersLimit.total} (attempted '
101+
'${updatedPreferences.savedHeadlineFilters.length}).',
102+
);
103+
throw ForbiddenException(
104+
'You have reached your limit of ${savedHeadlineFiltersLimit.total} '
105+
'saved headline filters.',
94106
);
95-
throw const ForbiddenException('Interest limits are not configured.');
96107
}
97108

98-
// Check total number of interests.
99-
if (updatedPreferences.interests.length > interestLimits.total) {
109+
// Validate the number of pinned saved headline filters.
110+
final pinnedHeadlineFilterCount = updatedPreferences.savedHeadlineFilters
111+
.where((f) => f.isPinned)
112+
.length;
113+
if (pinnedHeadlineFilterCount > savedHeadlineFiltersLimit.pinned) {
100114
_log.warning(
101-
'User ${user.id} exceeded total interest limit: '
102-
'${interestLimits.total} (attempted '
103-
'${updatedPreferences.interests.length}).',
115+
'User ${user.id} exceeded pinned saved headline filter limit: '
116+
'${savedHeadlineFiltersLimit.pinned} (attempted $pinnedHeadlineFilterCount).',
104117
);
105118
throw ForbiddenException(
106-
'You have reached your limit of ${interestLimits.total} saved interests.',
119+
'You have reached your limit of ${savedHeadlineFiltersLimit.pinned} '
120+
'pinned saved headline filters.',
107121
);
108122
}
109123

110-
// Check total number of pinned feed filters.
111-
final pinnedCount = updatedPreferences.interests
112-
.where((i) => i.isPinnedFeedFilter)
113-
.length;
114-
if (pinnedCount > interestLimits.pinnedFeedFilters) {
124+
// Validate notification subscription limits for each delivery type for saved headline filters.
125+
if (savedHeadlineFiltersLimit.notificationSubscriptions != null) {
126+
for (final deliveryType
127+
in PushNotificationSubscriptionDeliveryType.values) {
128+
final notificationLimit =
129+
savedHeadlineFiltersLimit.notificationSubscriptions![deliveryType];
130+
if (notificationLimit == null) {
131+
// This indicates a misconfiguration in RemoteConfig if a deliveryType is expected but not present.
132+
_log.severe(
133+
'Notification limit for type ${deliveryType.name} not configured for '
134+
'role ${user.appRole} in savedHeadlineFiltersLimit. Denying request.',
135+
);
136+
throw ForbiddenException(
137+
'Notification limits for ${deliveryType.name} are not configured '
138+
'for saved headline filters.',
139+
);
140+
}
141+
142+
final subscriptionCount = updatedPreferences.savedHeadlineFilters
143+
.where((f) => f.deliveryTypes.contains(deliveryType))
144+
.length;
145+
146+
if (subscriptionCount > notificationLimit) {
147+
_log.warning(
148+
'User ${user.id} exceeded notification limit for '
149+
'${deliveryType.name} in saved headline filters: $notificationLimit '
150+
'(attempted $subscriptionCount).',
151+
);
152+
throw ForbiddenException(
153+
'You have reached your limit of $notificationLimit '
154+
'${deliveryType.name} notification subscriptions for saved headline filters.',
155+
);
156+
}
157+
}
158+
}
159+
160+
// --- 3. Check saved source filter limits ---
161+
// Validate the total number of saved source filters.
162+
if (updatedPreferences.savedSourceFilters.length >
163+
savedSourceFiltersLimit.total) {
115164
_log.warning(
116-
'User ${user.id} exceeded pinned feed filter limit: '
117-
'${interestLimits.pinnedFeedFilters} (attempted $pinnedCount).',
165+
'User ${user.id} exceeded total saved source filter limit: '
166+
'${savedSourceFiltersLimit.total} (attempted '
167+
'${updatedPreferences.savedSourceFilters.length}).',
118168
);
119169
throw ForbiddenException(
120-
'You have reached your limit of ${interestLimits.pinnedFeedFilters} '
121-
'pinned feed filters.',
170+
'You have reached your limit of ${savedSourceFiltersLimit.total} '
171+
'saved source filters.',
122172
);
123173
}
124174

125-
// Check notification subscription limits for each possible delivery type.
126-
for (final deliveryType
127-
in PushNotificationSubscriptionDeliveryType.values) {
128-
final notificationLimit = interestLimits.notifications[deliveryType];
129-
if (notificationLimit == null) {
130-
_log.severe(
131-
'Notification limit for type ${deliveryType.name} not found for '
132-
'role ${user.appRole}. Denying request by default.',
133-
);
134-
throw ForbiddenException(
135-
'Notification limits for ${deliveryType.name} are not configured.',
136-
);
137-
}
138-
139-
// Count how many of the user's proposed interests include this type.
140-
final subscriptionCount = updatedPreferences.interests
141-
.where((i) => i.deliveryTypes.contains(deliveryType))
142-
.length;
143-
144-
if (subscriptionCount > notificationLimit) {
145-
_log.warning(
146-
'User ${user.id} exceeded notification limit for '
147-
'${deliveryType.name}: $notificationLimit '
148-
'(attempted $subscriptionCount).',
149-
);
150-
throw ForbiddenException(
151-
'You have reached your limit of $notificationLimit '
152-
'${deliveryType.name} notification subscriptions.',
153-
);
154-
}
175+
// Validate the number of pinned saved source filters.
176+
final pinnedSourceFilterCount = updatedPreferences.savedSourceFilters
177+
.where((f) => f.isPinned)
178+
.length;
179+
if (pinnedSourceFilterCount > savedSourceFiltersLimit.pinned) {
180+
_log.warning(
181+
'User ${user.id} exceeded pinned saved source filter limit: '
182+
'${savedSourceFiltersLimit.pinned} (attempted $pinnedSourceFilterCount).',
183+
);
184+
throw ForbiddenException(
185+
'You have reached your limit of ${savedSourceFiltersLimit.pinned} '
186+
'pinned saved source filters.',
187+
);
155188
}
156189

157190
_log.info(
158191
'All user content preferences limits check passed for user ${user.id}.',
159192
);
160193
}
161194

162-
/// Helper to get the correct limits based on the user's role.
163-
(int, int) _getLimitsForRole(
195+
/// Helper to retrieve all relevant user preference limits based on the user's role.
196+
///
197+
/// Throws [StateError] if a required limit is not configured for the given role,
198+
/// indicating a misconfiguration in the remote config.
199+
(
200+
int followedItemsLimit,
201+
int savedHeadlinesLimit,
202+
SavedFilterLimits savedHeadlineFiltersLimit,
203+
SavedFilterLimits savedSourceFiltersLimit,
204+
)
205+
_getLimitsForRole(
164206
AppUserRole role,
165207
UserPreferenceConfig limits,
166208
) {
167-
return switch (role) {
168-
AppUserRole.guestUser => (
169-
limits.guestFollowedItemsLimit,
170-
limits.guestSavedHeadlinesLimit,
171-
),
172-
AppUserRole.standardUser => (
173-
limits.authenticatedFollowedItemsLimit,
174-
limits.authenticatedSavedHeadlinesLimit,
175-
),
176-
AppUserRole.premiumUser => (
177-
limits.premiumFollowedItemsLimit,
178-
limits.premiumSavedHeadlinesLimit,
179-
),
180-
};
209+
final followedItemsLimit = limits.followedItemsLimit[role];
210+
if (followedItemsLimit == null) {
211+
throw StateError('Followed items limit not configured for role: $role');
212+
}
213+
214+
final savedHeadlinesLimit = limits.savedHeadlinesLimit[role];
215+
if (savedHeadlinesLimit == null) {
216+
throw StateError('Saved headlines limit not configured for role: $role');
217+
}
218+
219+
final savedHeadlineFiltersLimit = limits.savedHeadlineFiltersLimit[role];
220+
if (savedHeadlineFiltersLimit == null) {
221+
throw StateError(
222+
'Saved headline filters limit not configured for role: $role',
223+
);
224+
}
225+
226+
final savedSourceFiltersLimit = limits.savedSourceFiltersLimit[role];
227+
if (savedSourceFiltersLimit == null) {
228+
throw StateError(
229+
'Saved source filters limit not configured for role: $role',
230+
);
231+
}
232+
233+
return (
234+
followedItemsLimit,
235+
savedHeadlinesLimit,
236+
savedHeadlineFiltersLimit,
237+
savedSourceFiltersLimit,
238+
);
181239
}
182240
}

0 commit comments

Comments
 (0)