Skip to content

Commit 7fd22a2

Browse files
committed
feat(services): implement engagement and report limit checks
Overhauls the `DefaultUserActionLimitService` to correctly enforce daily limits for engagements and reports. The service now injects the `Engagement` and `Report` repositories to perform database counts. - `checkEngagementCreationLimit`: Checks the `reactionsPerDay` limit on every call. If the engagement contains a comment, it also checks the `commentsPerDay` limit. - `checkReportCreationLimit`: Checks the `reportsPerDay` limit. This centralizes all UGC limit logic within the service layer, adhering to the established architectural pattern.
1 parent 9248839 commit 7fd22a2

File tree

1 file changed

+116
-9
lines changed

1 file changed

+116
-9
lines changed

lib/src/services/default_user_preference_limit_service.dart renamed to lib/src/services/default_user_action_limit_service.dart

Lines changed: 116 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
import 'package:core/core.dart';
22
import 'package:data_repository/data_repository.dart';
3-
import 'package:flutter_news_app_api_server_full_source_code/src/services/user_preference_limit_service.dart';
3+
import 'package:flutter_news_app_api_server_full_source_code/src/services/user_action_limit_service.dart';
44
import 'package:logging/logging.dart';
55

6-
/// {@template default_user_preference_limit_service}
7-
/// Default implementation of [UserPreferenceLimitService] that enforces limits
8-
/// based on user role and the `InterestConfig` and `UserPreferenceConfig`
6+
/// {@template default_user_action_limit_service}
7+
/// Default implementation of [UserActionLimitService] that enforces limits
8+
/// based on user role and the `UserLimitsConfig`
99
/// sections within the application's [RemoteConfig].
1010
/// {@endtemplate}
11-
class DefaultUserPreferenceLimitService implements UserPreferenceLimitService {
12-
/// {@macro default_user_preference_limit_service}
13-
const DefaultUserPreferenceLimitService({
11+
class DefaultUserActionLimitService implements UserActionLimitService {
12+
/// {@macro default_user_action_limit_service}
13+
const DefaultUserActionLimitService({
1414
required DataRepository<RemoteConfig> remoteConfigRepository,
15+
required DataRepository<Engagement> engagementRepository,
16+
required DataRepository<Report> reportRepository,
1517
required Logger log,
1618
}) : _remoteConfigRepository = remoteConfigRepository,
19+
_engagementRepository = engagementRepository,
20+
_reportRepository = reportRepository,
1721
_log = log;
1822

1923
final DataRepository<RemoteConfig> _remoteConfigRepository;
24+
final DataRepository<Engagement> _engagementRepository;
25+
final DataRepository<Report> _reportRepository;
2026
final Logger _log;
2127

2228
// Assuming a fixed ID for the RemoteConfig document
@@ -28,7 +34,7 @@ class DefaultUserPreferenceLimitService implements UserPreferenceLimitService {
2834
required UserContentPreferences updatedPreferences,
2935
}) async {
3036
_log.info(
31-
'Checking all user content preferences limits for user ${user.id}.',
37+
'Checking all user action limits for user ${user.id}.',
3238
);
3339
final remoteConfig = await _remoteConfigRepository.read(
3440
id: _remoteConfigId,
@@ -47,6 +53,8 @@ class DefaultUserPreferenceLimitService implements UserPreferenceLimitService {
4753
);
4854

4955
// --- 1. Check general preference limits ---
56+
// Note: The checks for commentsPerDay and reportsPerDay are not performed
57+
// here. They are action-based and enforced by the RateLimitService.
5058
if (updatedPreferences.followedCountries.length > followedItemsLimit) {
5159
_log.warning(
5260
'User ${user.id} exceeded followed countries limit: '
@@ -202,7 +210,7 @@ class DefaultUserPreferenceLimitService implements UserPreferenceLimitService {
202210
SavedFilterLimits savedHeadlineFiltersLimit,
203211
SavedFilterLimits savedSourceFiltersLimit,
204212
)
205-
_getLimitsForRole(
213+
_getPreferenceLimitsForRole(
206214
AppUserRole role,
207215
UserLimitsConfig limits,
208216
) {
@@ -237,4 +245,103 @@ class DefaultUserPreferenceLimitService implements UserPreferenceLimitService {
237245
savedSourceFiltersLimit,
238246
);
239247
}
248+
249+
@override
250+
Future<void> checkEngagementCreationLimit({
251+
required User user,
252+
required Engagement engagement,
253+
}) async {
254+
_log.info('Checking engagement creation limits for user ${user.id}.');
255+
final remoteConfig = await _remoteConfigRepository.read(id: _remoteConfigId);
256+
final limits = remoteConfig.user.limits;
257+
258+
// --- 1. Check Reaction Limit ---
259+
final reactionsLimit = limits.reactionsPerDay[user.appRole];
260+
if (reactionsLimit == null) {
261+
throw StateError(
262+
'Reactions per day limit not configured for role: ${user.appRole}',
263+
);
264+
}
265+
266+
// Count all engagements in the last 24 hours for the reaction limit.
267+
final twentyFourHoursAgo = DateTime.now().subtract(const Duration(hours: 24));
268+
final reactionCount = await _engagementRepository.count(
269+
filter: {
270+
'userId': user.id,
271+
'createdAt': {r'$gte': twentyFourHoursAgo.toIso8601String()},
272+
},
273+
);
274+
275+
if (reactionCount >= reactionsLimit) {
276+
_log.warning(
277+
'User ${user.id} exceeded reactions per day limit: $reactionsLimit.',
278+
);
279+
throw ForbiddenException(
280+
'You have reached your daily limit for reactions.',
281+
);
282+
}
283+
284+
// --- 2. Check Comment Limit (only if a comment is present) ---
285+
if (engagement.comment != null) {
286+
final commentsLimit = limits.commentsPerDay[user.appRole];
287+
if (commentsLimit == null) {
288+
throw StateError(
289+
'Comments per day limit not configured for role: ${user.appRole}',
290+
);
291+
}
292+
293+
// Count engagements with comments in the last 24 hours.
294+
final commentCount = await _engagementRepository.count(
295+
filter: {
296+
'userId': user.id,
297+
'comment': {r'$exists': true, r'$ne': null},
298+
'createdAt': {r'$gte': twentyFourHoursAgo.toIso8601String()},
299+
},
300+
);
301+
302+
if (commentCount >= commentsLimit) {
303+
_log.warning(
304+
'User ${user.id} exceeded comments per day limit: $commentsLimit.',
305+
);
306+
throw ForbiddenException(
307+
'You have reached your daily limit for comments.',
308+
);
309+
}
310+
}
311+
312+
_log.info(
313+
'Engagement creation limit checks passed for user ${user.id}.',
314+
);
315+
}
316+
317+
@override
318+
Future<void> checkReportCreationLimit({required User user}) async {
319+
_log.info('Checking report creation limits for user ${user.id}.');
320+
final remoteConfig = await _remoteConfigRepository.read(id: _remoteConfigId);
321+
final limits = remoteConfig.user.limits;
322+
323+
final reportsLimit = limits.reportsPerDay[user.appRole];
324+
if (reportsLimit == null) {
325+
throw StateError(
326+
'Reports per day limit not configured for role: ${user.appRole}',
327+
);
328+
}
329+
330+
final twentyFourHoursAgo = DateTime.now().subtract(const Duration(hours: 24));
331+
final reportCount = await _reportRepository.count(
332+
filter: {
333+
'reporterUserId': user.id,
334+
'createdAt': {r'$gte': twentyFourHoursAgo.toIso8601String()},
335+
},
336+
);
337+
338+
if (reportCount >= reportsLimit) {
339+
_log.warning(
340+
'User ${user.id} exceeded reports per day limit: $reportsLimit.',
341+
);
342+
throw ForbiddenException('You have reached your daily limit for reports.');
343+
}
344+
345+
_log.info('Report creation limit checks passed for user ${user.id}.');
346+
}
240347
}

0 commit comments

Comments
 (0)