Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
70c1339
build(deps): update core dependency
fulleni Nov 25, 2025
cbdfa87
refactor(dependencies): update userAppSettings repository and model n…
fulleni Nov 25, 2025
efd24c3
refactor(data): update UserAppSettings to AppSettings
fulleni Nov 25, 2025
9c0a23d
refactor(registry): update model config for app settings
fulleni Nov 25, 2025
8a4b5e5
fix(database): rename user_app_settings collection to app_settings
fulleni Nov 25, 2025
84d99dd
refactor(auth): update AuthService and related components
fulleni Nov 25, 2025
e7150b3
fix(database): update seeding service for app settings and ad config
fulleni Nov 25, 2025
5260c71
refactor(limit): update user limits configuration retrieval
fulleni Nov 25, 2025
a72e689
fix(firebase_push_notification_client): correct notification payload …
fulleni Nov 25, 2025
7e4e077
fix(onesignal): correct payload mapping and enhancement
fulleni Nov 25, 2025
1349628
refactor(push-notification): update notification payload structure
fulleni Nov 25, 2025
ffd81d0
fix(push-notification): update push notification configuration path
fulleni Nov 25, 2025
d7366ed
feat(models): add push notification request models
fulleni Nov 25, 2025
e086620
refactor(push_notification): update FirebaseMessage data payload type
fulleni Nov 25, 2025
ecde6b6
refactor(push_notification): update data payload type in OneSignalReq…
fulleni Nov 25, 2025
d15de37
refactor(push-notification): use FirebaseRequestBody model for notifi…
fulleni Nov 25, 2025
ee0483c
refactor(push-notification): use OneSignalRequestBody model for API r…
fulleni Nov 25, 2025
012443f
build(dev_tools): update dependencies and add build tools
fulleni Nov 25, 2025
1b7e0f4
ci: ignore cast_nullable_to_non_nullable lint rule
fulleni Nov 25, 2025
c937a71
build(models): add generated JSON serialization code for push notific…
fulleni Nov 25, 2025
172597f
fix(models): add createFactory: false to JsonSerializable annotation
fulleni Nov 25, 2025
f44e32c
build(serialization): generate
fulleni Nov 25, 2025
0c1a585
refactor(routes): update dependency type in middleware
fulleni Nov 25, 2025
6a69485
style: format
fulleni Nov 25, 2025
67d2496
chore: misc
fulleni Nov 25, 2025
25466da
fix(firebase_messaging): make notification body optional
fulleni Nov 25, 2025
0c96edc
fix(onesignal): make notification contents optional
fulleni Nov 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ analyzer:
document_ignores: ignore
one_member_abstracts: ignore
cascade_invocations: ignore
cast_nullable_to_non_nullable: ignore
exclude:
- build/**
linter:
Expand Down
16 changes: 8 additions & 8 deletions lib/src/config/app_dependencies.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class AppDependencies {
late final DataRepository<Country> countryRepository;
late final DataRepository<Language> languageRepository;
late final DataRepository<User> userRepository;
late final DataRepository<UserAppSettings> userAppSettingsRepository;
late final DataRepository<AppSettings> appSettingsRepository;
late final DataRepository<UserContentPreferences>
userContentPreferencesRepository;
late final DataRepository<PushNotificationDevice>
Expand Down Expand Up @@ -190,12 +190,12 @@ class AppDependencies {
toJson: (item) => item.toJson(),
logger: Logger('DataMongodb<User>'),
);
final userAppSettingsClient = DataMongodb<UserAppSettings>(
final appSettingsClient = DataMongodb<AppSettings>(
connectionManager: _mongoDbConnectionManager,
modelName: 'user_app_settings',
fromJson: UserAppSettings.fromJson,
modelName: 'app_settings',
fromJson: AppSettings.fromJson,
toJson: (item) => item.toJson(),
logger: Logger('DataMongodb<UserAppSettings>'),
logger: Logger('DataMongodb<AppSettings>'),
);
final userContentPreferencesClient = DataMongodb<UserContentPreferences>(
connectionManager: _mongoDbConnectionManager,
Expand Down Expand Up @@ -306,8 +306,8 @@ class AppDependencies {
countryRepository = DataRepository(dataClient: countryClient);
languageRepository = DataRepository(dataClient: languageClient);
userRepository = DataRepository(dataClient: userClient);
userAppSettingsRepository = DataRepository(
dataClient: userAppSettingsClient,
appSettingsRepository = DataRepository(
dataClient: appSettingsClient,
);
userContentPreferencesRepository = DataRepository(
dataClient: userContentPreferencesClient,
Expand Down Expand Up @@ -359,7 +359,7 @@ class AppDependencies {
verificationCodeStorageService: verificationCodeStorageService,
permissionService: permissionService,
emailRepository: emailRepository,
userAppSettingsRepository: userAppSettingsRepository,
appSettingsRepository: appSettingsRepository,
userContentPreferencesRepository: userContentPreferencesRepository,
log: Logger('AuthService'),
);
Expand Down
2 changes: 2 additions & 0 deletions lib/src/models/models.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export 'push_notification/push_notification.dart';
export 'request_id.dart';
95 changes: 95 additions & 0 deletions lib/src/models/push_notification/firebase_request_body.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import 'package:core/core.dart';
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';

part 'firebase_request_body.g.dart';

/// {@template firebase_request_body}
/// Represents the top-level structure for a Firebase Cloud Messaging
/// v1 API request.
/// {@endtemplate}
@JsonSerializable(
explicitToJson: true,
includeIfNull: true,
checked: true,
createFactory: false,
)
class FirebaseRequestBody extends Equatable {
/// {@macro firebase_request_body}
const FirebaseRequestBody({required this.message});

/// The message payload.
final FirebaseMessage message;

/// Converts this [FirebaseRequestBody] instance to a JSON map.
Map<String, dynamic> toJson() => _$FirebaseRequestBodyToJson(this);

@override
List<Object> get props => [message];
}

/// {@template firebase_message}
/// Represents the message object within a Firebase request.
/// {@endtemplate}
@JsonSerializable(
explicitToJson: true,
includeIfNull: true,
checked: true,
createFactory: false,
)
class FirebaseMessage extends Equatable {
/// {@macro firebase_message}
const FirebaseMessage({
required this.token,
required this.notification,
required this.data,
});

/// The registration token of the device to send the message to.
final String token;

/// The notification content.
final FirebaseNotification notification;

/// The custom data payload.
final PushNotificationPayload data;

/// Converts this [FirebaseMessage] instance to a JSON map.
Map<String, dynamic> toJson() => _$FirebaseMessageToJson(this);

@override
List<Object> get props => [token, notification, data];
}

/// {@template firebase_notification}
/// Represents the notification content within a Firebase message.
/// {@endtemplate}
@JsonSerializable(
explicitToJson: true,
includeIfNull: true,
checked: true,
createFactory: false,
)
class FirebaseNotification extends Equatable {
/// {@macro firebase_notification}
const FirebaseNotification({
required this.title,
this.body,
this.image,
});

/// The notification's title.
final String title;

/// The notification's body text.
final String? body;

/// The URL of an image to be displayed in the notification.
final String? image;

/// Converts this [FirebaseNotification] instance to a JSON map.
Map<String, dynamic> toJson() => _$FirebaseNotificationToJson(this);

@override
List<Object?> get props => [title, body, image];
}
37 changes: 37 additions & 0 deletions lib/src/models/push_notification/firebase_request_body.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions lib/src/models/push_notification/onesignal_request_body.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'package:core/core.dart';
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';

part 'onesignal_request_body.g.dart';

/// {@template onesignal_request_body}
/// Represents the request body for the OneSignal /notifications endpoint.
/// {@endtemplate}
@JsonSerializable(
explicitToJson: true,
includeIfNull: false, // Do not include null fields in the JSON
checked: true,
fieldRename: FieldRename.snake,
createFactory: false,
)
class OneSignalRequestBody extends Equatable {
/// {@macro onesignal_request_body}
const OneSignalRequestBody({
required this.appId,
required this.includePlayerIds,
required this.headings,
required this.data,
this.contents,
this.bigPicture,
});

/// The OneSignal App ID.
final String appId;

/// A list of OneSignal Player IDs to send the notification to.
final List<String> includePlayerIds;

/// The notification's title.
final Map<String, String> headings;

/// The notification's content.
final Map<String, String>? contents;

/// The custom data payload
final PushNotificationPayload data;

/// The URL of a large image to display in the notification.
final String? bigPicture;

/// Converts this [OneSignalRequestBody] instance to a JSON map.
Map<String, dynamic> toJson() => _$OneSignalRequestBodyToJson(this);

@override
List<Object?> get props => [
appId,
includePlayerIds,
headings,
contents,
data,
bigPicture,
];
}
21 changes: 21 additions & 0 deletions lib/src/models/push_notification/onesignal_request_body.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions lib/src/models/push_notification/push_notification.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export 'firebase_request_body.dart';
export 'onesignal_request_body.dart';
5 changes: 2 additions & 3 deletions lib/src/rbac/permissions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,8 @@ abstract class Permissions {
static const String userUpdate = 'user.update';

// User App Settings Permissions (User-owned)
static const String userAppSettingsReadOwned = 'user_app_settings.read_owned';
static const String userAppSettingsUpdateOwned =
'user_app_settings.update_owned';
static const String appSettingsReadOwned = 'app_settings.read_owned';
static const String appSettingsUpdateOwned = 'app_settings.update_owned';

// User Content Preferences Permissions (User-owned)
static const String userContentPreferencesReadOwned =
Expand Down
4 changes: 2 additions & 2 deletions lib/src/rbac/role_permissions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ final Set<String> _appGuestUserPermissions = {
Permissions.sourceRead,
Permissions.countryRead,
Permissions.languageRead,
Permissions.userAppSettingsReadOwned,
Permissions.userAppSettingsUpdateOwned,
Permissions.appSettingsReadOwned,
Permissions.appSettingsUpdateOwned,
Permissions.userContentPreferencesReadOwned,
Permissions.userContentPreferencesUpdateOwned,
Permissions.remoteConfigRead,
Expand Down
14 changes: 7 additions & 7 deletions lib/src/registry/data_operation_registry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ class DataOperationRegistry {
c.read<DataRepository<Language>>().read(id: id, userId: null),
'user': (c, id) =>
c.read<DataRepository<User>>().read(id: id, userId: null),
'user_app_settings': (c, id) =>
c.read<DataRepository<UserAppSettings>>().read(id: id, userId: null),
'app_settings': (c, id) =>
c.read<DataRepository<AppSettings>>().read(id: id, userId: null),
'user_content_preferences': (c, id) => c
.read<DataRepository<UserContentPreferences>>()
.read(id: id, userId: null),
Expand Down Expand Up @@ -389,9 +389,9 @@ class DataOperationRegistry {
userId: uid,
);
},
'user_app_settings': (c, id, item, uid) => c
.read<DataRepository<UserAppSettings>>()
.update(id: id, item: item as UserAppSettings, userId: uid),
'app_settings': (c, id, item, uid) => c
.read<DataRepository<AppSettings>>()
.update(id: id, item: item as AppSettings, userId: uid),
'user_content_preferences': (context, id, item, uid) async {
_log.info(
'Executing custom updater for user_content_preferences ID: $id.',
Expand Down Expand Up @@ -454,8 +454,8 @@ class DataOperationRegistry {
c.read<DataRepository<Country>>().delete(id: id, userId: uid),
'language': (c, id, uid) =>
c.read<DataRepository<Language>>().delete(id: id, userId: uid),
'user_app_settings': (c, id, uid) =>
c.read<DataRepository<UserAppSettings>>().delete(id: id, userId: uid),
'app_settings': (c, id, uid) =>
c.read<DataRepository<AppSettings>>().delete(id: id, userId: uid),
'user_content_preferences': (c, id, uid) => c
.read<DataRepository<UserContentPreferences>>()
.delete(id: id, userId: uid),
Expand Down
15 changes: 7 additions & 8 deletions lib/src/registry/model_registry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -305,37 +305,36 @@ final modelRegistry = <String, ModelConfig<dynamic>>{
type: RequiredPermissionType.unsupported,
),
),
'user_app_settings': ModelConfig<UserAppSettings>(
fromJson: UserAppSettings.fromJson,
'app_settings': ModelConfig<AppSettings>(
fromJson: AppSettings.fromJson,
getId: (s) => s.id,
getOwnerId: (dynamic item) =>
(item as UserAppSettings).id as String?, // User ID is the owner ID
getOwnerId: (dynamic item) => (item as AppSettings).id,
getCollectionPermission: const ModelActionPermission(
type: RequiredPermissionType.unsupported, // Not accessible via collection
requiresAuthentication: true,
),
getItemPermission: const ModelActionPermission(
type: RequiredPermissionType.specificPermission,
permission: Permissions.userAppSettingsReadOwned,
permission: Permissions.appSettingsReadOwned,
requiresOwnershipCheck: true,
requiresAuthentication: true,
),
postPermission: const ModelActionPermission(
type: RequiredPermissionType.unsupported,
requiresAuthentication: true,
// Creation of UserAppSettings is handled by the authentication service
// Creation of AppSettings is handled by the authentication service
// during user creation, not via a direct POST to /api/v1/data.
),
putPermission: const ModelActionPermission(
type: RequiredPermissionType.specificPermission,
permission: Permissions.userAppSettingsUpdateOwned,
permission: Permissions.appSettingsUpdateOwned,
requiresOwnershipCheck: true,
requiresAuthentication: true,
),
deletePermission: const ModelActionPermission(
type: RequiredPermissionType.unsupported,
requiresAuthentication: true,
// Deletion of UserAppSettings is handled by the authentication service
// Deletion of AppSettings is handled by the authentication service
// during account deletion, not via a direct DELETE to /api/v1/data.
),
),
Expand Down
Loading
Loading