Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 0 additions & 8 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
<!--
Thanks for contributing!

Provide a description of your changes below and a general summary in the title

Please look at the following checklist to ensure that your PR can be accepted quickly:
-->

## Status

**READY/IN DEVELOPMENT/HOLD**
Expand Down
11 changes: 0 additions & 11 deletions lib/src/config/app_dependencies.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ class AppDependencies {
late final DataRepository<UserContentPreferences>
userContentPreferencesRepository;
late final DataRepository<RemoteConfig> remoteConfigRepository;
late final DataRepository<LocalAd> localAdRepository;
late final EmailRepository emailRepository;

// Services
Expand Down Expand Up @@ -232,16 +231,6 @@ class AppDependencies {

emailRepository = EmailRepository(emailClient: emailClient);

final localAdClient = DataMongodb<LocalAd>(
connectionManager: _mongoDbConnectionManager,
modelName: 'local_ads',
fromJson: LocalAd.fromJson,
toJson: LocalAd.toJson,
searchableFields: ['title'],
logger: Logger('DataMongodb<LocalAd>'),
);
localAdRepository = DataRepository(dataClient: localAdClient);

// 5. Initialize Services
tokenBlacklistService = MongoDbTokenBlacklistService(
connectionManager: _mongoDbConnectionManager,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'package:flutter_news_app_api_server_full_source_code/src/database/migration.dart';
import 'package:logging/logging.dart';
import 'package:mongo_dart/mongo_dart.dart';

/// Migration to remove the legacy `local` ad platform from the `remote_configs`
/// collection.
///
/// This migration performs two critical cleanup tasks:
/// 1. It removes the `local` key from the `adConfig.platformAdIdentifiers` map
/// in all `remote_configs` documents.
/// 2. It updates any `remote_configs` document where the `primaryAdPlatform`
/// is set to `local`, changing it to `admob`.
///
/// This ensures data consistency after the removal of the `AdPlatformType.local`
/// enum value and prevents deserialization errors in the application.
class RemoveLocalAdPlatform extends Migration {
/// {@macro remove_local_ad_platform}
RemoveLocalAdPlatform()
: super(
prDate: '20251103073226',
prId: '57',
prSummary:
'Removes the legacy local ad platform from the remote config, migrating existing data to use admob as the default.',
);

@override
Future<void> up(Db db, Logger log) async {
final collection = db.collection('remote_configs');

// Step 1: Unset the 'local' key from the platformAdIdentifiers map.
// This removes the field entirely from any document where it exists.
log.info(
'Attempting to remove "adConfig.platformAdIdentifiers.local" field...',
);
final unsetResult = await collection.updateMany(
where.exists('adConfig.platformAdIdentifiers.local'),
modify.unset('adConfig.platformAdIdentifiers.local'),
);
log.info(
'Removed "adConfig.platformAdIdentifiers.local" from ${unsetResult.nModified} documents.',
);

// Step 2: Update the primaryAdPlatform from 'local' to 'admob'.
// This ensures that no document is left with an invalid primary platform.
log.info(
'Attempting to migrate primaryAdPlatform from "local" to "admob"...',
);
final updateResult = await collection.updateMany(
where.eq('adConfig.primaryAdPlatform', 'local'),
modify.set('adConfig.primaryAdPlatform', 'admob'),
);
log.info(
'Migrated primaryAdPlatform to "admob" for ${updateResult.nModified} documents.',
);
}

@override
Future<void> down(Db db, Logger log) async {
// Reverting this change is not safe as it would require re-introducing
// an enum value that no longer exists in the code.
log.warning(
'Reverting "RemoveLocalAdPlatform" is not supported. The "local" ad platform configuration would need to be manually restored if required.',
);
}
}
2 changes: 2 additions & 0 deletions lib/src/database/migrations/all_migrations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:flutter_news_app_api_server_full_source_code/src/database/migrat
import 'package:flutter_news_app_api_server_full_source_code/src/database/migrations/20251013000056_add_saved_filters_to_user_preferences.dart';
import 'package:flutter_news_app_api_server_full_source_code/src/database/migrations/20251013000057_add_saved_filters_to_remote_config.dart';
import 'package:flutter_news_app_api_server_full_source_code/src/database/migrations/20251024000000_add_logo_url_to_sources.dart';
import 'package:flutter_news_app_api_server_full_source_code/src/database/migrations/20251103073226_remove_local_ad_platform.dart';
import 'package:flutter_news_app_api_server_full_source_code/src/services/database_migration_service.dart'
show DatabaseMigrationService;

Expand All @@ -16,4 +17,5 @@ final List<Migration> allMigrations = [
AddSavedFiltersToUserPreferences(),
AddSavedFiltersToRemoteConfig(),
AddLogoUrlToSources(),
RemoveLocalAdPlatform(),
];
22 changes: 5 additions & 17 deletions lib/src/registry/data_operation_registry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ typedef ItemUpdater =
typedef ItemDeleter =
Future<void> Function(RequestContext context, String id, String? userId);

final _log = Logger('DataOperationRegistry');

/// {@template data_operation_registry}
/// A centralized registry for all data handling functions (CRUD operations).
///
Expand All @@ -56,12 +58,12 @@ typedef ItemDeleter =
/// data operations are performed for each model, improving consistency across
/// the API.
/// {@endtemplate}

final _log = Logger('DataOperationRegistry');

class DataOperationRegistry {
/// {@macro data_operation_registry}
DataOperationRegistry() {
_log.info(
'Initializing DataOperationRegistry.',
);
_registerOperations();
}

Expand Down Expand Up @@ -113,8 +115,6 @@ class DataOperationRegistry {
.read(id: id, userId: null),
'remote_config': (c, id) =>
c.read<DataRepository<RemoteConfig>>().read(id: id, userId: null),
'local_ad': (c, id) =>
c.read<DataRepository<LocalAd>>().read(id: id, userId: null),
'dashboard_summary': (c, id) =>
c.read<DashboardSummaryService>().getSummary(),
});
Expand Down Expand Up @@ -166,9 +166,6 @@ class DataOperationRegistry {
sort: s,
pagination: p,
),
'local_ad': (c, uid, f, s, p) => c
.read<DataRepository<LocalAd>>()
.readAll(userId: uid, filter: f, sort: s, pagination: p),
});

// --- Register Item Creators ---
Expand Down Expand Up @@ -196,10 +193,6 @@ class DataOperationRegistry {
'remote_config': (c, item, uid) => c
.read<DataRepository<RemoteConfig>>()
.create(item: item as RemoteConfig, userId: uid),
'local_ad': (c, item, uid) => c.read<DataRepository<LocalAd>>().create(
item: item as LocalAd,
userId: uid,
),
});

// --- Register Item Updaters ---
Expand Down Expand Up @@ -319,9 +312,6 @@ class DataOperationRegistry {
'remote_config': (c, id, item, uid) => c
.read<DataRepository<RemoteConfig>>()
.update(id: id, item: item as RemoteConfig, userId: uid),
'local_ad': (c, id, item, uid) => c
.read<DataRepository<LocalAd>>()
.update(id: id, item: item as LocalAd, userId: uid),
});

// --- Register Item Deleters ---
Expand All @@ -343,8 +333,6 @@ class DataOperationRegistry {
.delete(id: id, userId: uid),
'remote_config': (c, id, uid) =>
c.read<DataRepository<RemoteConfig>>().delete(id: id, userId: uid),
'local_ad': (c, id, uid) =>
c.read<DataRepository<LocalAd>>().delete(id: id, userId: uid),
});
}
}
30 changes: 0 additions & 30 deletions lib/src/registry/model_registry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -425,36 +425,6 @@ final modelRegistry = <String, ModelConfig<dynamic>>{
requiresAuthentication: true,
),
),
'local_ad': ModelConfig<LocalAd>(
fromJson: LocalAd.fromJson,
getId: (ad) => ad.id,
getOwnerId: null, // LocalAd is a global resource, not user-owned
getCollectionPermission: const ModelActionPermission(
type: RequiredPermissionType.specificPermission,
permission: Permissions.localAdRead,
requiresAuthentication: true,
),
getItemPermission: const ModelActionPermission(
type: RequiredPermissionType.specificPermission,
permission: Permissions.localAdRead,
requiresAuthentication: true,
),
postPermission: const ModelActionPermission(
type: RequiredPermissionType.adminOnly,
permission: Permissions.localAdCreate,
requiresAuthentication: true,
),
putPermission: const ModelActionPermission(
type: RequiredPermissionType.adminOnly,
permission: Permissions.localAdUpdate,
requiresAuthentication: true,
),
deletePermission: const ModelActionPermission(
type: RequiredPermissionType.adminOnly,
permission: Permissions.localAdDelete,
requiresAuthentication: true,
),
),
};

/// Type alias for the ModelRegistry map for easier provider usage.
Expand Down
16 changes: 1 addition & 15 deletions lib/src/services/database_seeding_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,6 @@ class DatabaseSeedingService {
getId: (item) => item.id,
toJson: (item) => item.toJson(),
);
await _seedCollection<LocalAd>(
collectionName: 'local_ads',
fixtureData: localAdsFixturesData,
getId: (ad) => ad.id,
// ignore: unnecessary_lambdas
toJson: (item) => LocalAd.toJson(item),
);

_log.info('Database seeding process completed.');
}
Expand Down Expand Up @@ -130,7 +123,7 @@ class DatabaseSeedingService {
// Ensure primaryAdPlatform is not 'demo' for initial setup
// since its not intended for any use outside the mobile client.
final productionReadyAdConfig = initialConfig.adConfig.copyWith(
primaryAdPlatform: AdPlatformType.local,
primaryAdPlatform: AdPlatformType.admob,
);

final productionReadyConfig = initialConfig.copyWith(
Expand Down Expand Up @@ -191,13 +184,6 @@ class DatabaseSeedingService {
.collection('countries')
.createIndex(keys: {'name': 1}, name: 'countries_name_index');

/// Index for searching local ads by adType.
/// This index supports efficient queries and filtering on the 'adType' field
/// of local ad documents.
await _db
.collection('local_ads')
.createIndex(keys: {'adType': 1}, name: 'local_ads_adType_index');

// --- TTL and Unique Indexes via runCommand ---
// The following indexes are created using the generic `runCommand` because
// they require specific options not exposed by the simpler `createIndex`
Expand Down
12 changes: 6 additions & 6 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: analyzer
sha256: a40a0cee526a7e1f387c6847bd8a5ccbf510a75952ef8a28338e989558072cb0
sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08
url: "https://pub.dev"
source: hosted
version: "8.4.0"
version: "8.4.1"
archive:
dependency: transitive
description:
Expand Down Expand Up @@ -117,8 +117,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "v1.3.1"
resolved-ref: a03efff11b7577974cb444a1a6a46fee8b05ea42
ref: e7c808c9d459233196e2eac3137a9c87d3976af3
resolved-ref: e7c808c9d459233196e2eac3137a9c87d3976af3
url: "https://github.com/flutter-news-app-full-source-code/core.git"
source: git
version: "1.3.1"
Expand All @@ -142,10 +142,10 @@ packages:
dependency: "direct main"
description:
name: dart_frog
sha256: "0fc909d10ae79dd069e6a3a4769aeaa9839c93b7f6f1e71cfd37b276410875e7"
sha256: "87b5280b029aa2f80f5e9954db4b6c7cbbc2ae6adf5579150c11c40829f95a0a"
url: "https://pub.dev"
source: hosted
version: "1.2.4"
version: "1.2.5"
dart_jsonwebtoken:
dependency: "direct main"
description:
Expand Down
5 changes: 5 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,8 @@ dev_dependencies:
test: ^1.25.5
very_good_analysis: ^9.0.0

dependency_overrides:
core:
git:
url: https://github.com/flutter-news-app-full-source-code/core.git
ref: e7c808c9d459233196e2eac3137a9c87d3976af3
5 changes: 0 additions & 5 deletions routes/_middleware.dart
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,6 @@ Handler middleware(Handler handler) {
(_) => deps.remoteConfigRepository,
),
)
.use(
provider<DataRepository<LocalAd>>(
(_) => deps.localAdRepository,
),
)
.use(provider<EmailRepository>((_) => deps.emailRepository))
.use(
provider<TokenBlacklistService>(
Expand Down
10 changes: 4 additions & 6 deletions routes/api/v1/data/_middleware.dart
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,12 @@ Handler middleware(Handler handler) {
// route handler).
//
// 5. Actual Route Handler (from `index.dart` or `[id].dart`):
// - This runs last, only if all preceding middlewares pass. It will have
// access to a non-null `User` (if authenticated), `ModelConfig`, and
// `modelName` from the context.
// - It performs the data operation and any necessary handler-level
// ownership checks (if flagged by `ModelActionPermission.requiresOwnershipCheck`).
// - This runs last, only if all preceding middlewares pass. It has
// access to a `User?`, `ModelConfig`, and `modelName` from the context.
// - It performs the final data operation.
//
return handler
.use(authorizationMiddleware()) // Applied fourth (inner-most)
.use(authorizationMiddleware()) // Applied fourth (innermost)
.use(_dataRateLimiterMiddleware()) // Applied third
.use(_conditionalAuthenticationMiddleware()) // Applied second
.use(
Expand Down
Loading