@@ -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