From c46ed27a36c692fa40e600ec4d161581a3d5d5bb Mon Sep 17 00:00:00 2001
From: Christian Fiebrig <fiebrig@bitidee.de>
Date: Thu, 23 Jan 2025 09:33:36 +0100
Subject: [PATCH] apply door to use cache

---
 lib/app/services/converters.dart              |   4 +
 .../converters/campaign_action_parsing.dart   |  18 +-
 .../converters/door_create_model_parsing.dart |  23 ++
 .../converters/door_update_model_parsing.dart |  21 ++
 .../map_string_dynamic_converter.dart         |   4 +-
 lib/app/services/converters/poi_parsing.dart  |   1 +
 .../converters/poi_service_type_parsing.dart  |  33 +++
 .../poster_create_model_parsing.dart          |   4 +-
 .../poster_update_model_parsing.dart          |   2 +-
 .../gruene_api_campaigns_service.dart         |  49 +----
 lib/app/services/gruene_api_door_service.dart |  45 ++++
 .../services/gruene_api_poster_service.dart   |  10 +
 .../helper/campaign_action_cache.dart         | 208 ++++++++++++++----
 .../models/doors/door_create_model.dart       |  10 +
 .../models/doors/door_detail_model.dart       |  37 ++++
 .../models/doors/door_update_model.dart       |  24 +-
 .../models/flyer/flyer_detail_model.dart      |   5 +-
 lib/features/campaigns/screens/door_edit.dart |   2 +
 .../campaigns/screens/doors_screen.dart       |  31 ++-
 .../campaigns/screens/flyer_screen.dart       |   9 +-
 .../campaigns/screens/map_consumer.dart       |  19 +-
 .../campaigns/screens/posters_screen.dart     |  29 +--
 lib/main.dart                                 |   6 +-
 23 files changed, 443 insertions(+), 151 deletions(-)
 create mode 100644 lib/app/services/converters/door_create_model_parsing.dart
 create mode 100644 lib/app/services/converters/door_update_model_parsing.dart
 create mode 100644 lib/app/services/gruene_api_door_service.dart

diff --git a/lib/app/services/converters.dart b/lib/app/services/converters.dart
index 754e0682..c137a578 100644
--- a/lib/app/services/converters.dart
+++ b/lib/app/services/converters.dart
@@ -3,7 +3,9 @@ import 'dart:convert';
 import 'package:gruene_app/app/services/enums.dart';
 import 'package:gruene_app/app/services/nominatim_service.dart';
 import 'package:gruene_app/features/campaigns/helper/campaign_action.dart';
+import 'package:gruene_app/features/campaigns/models/doors/door_create_model.dart';
 import 'package:gruene_app/features/campaigns/models/doors/door_detail_model.dart';
+import 'package:gruene_app/features/campaigns/models/doors/door_update_model.dart';
 import 'package:gruene_app/features/campaigns/models/flyer/flyer_detail_model.dart';
 import 'package:gruene_app/features/campaigns/models/map_layer_model.dart';
 import 'package:gruene_app/features/campaigns/models/marker_item_model.dart';
@@ -36,3 +38,5 @@ part 'converters/poster_update_model_parsing.dart';
 part 'converters/campaign_action_parsing.dart';
 part 'converters/map_string_dynamic_converter.dart';
 part 'converters/string_extension.dart';
+part 'converters/door_update_model_parsing.dart';
+part 'converters/door_create_model_parsing.dart';
diff --git a/lib/app/services/converters/campaign_action_parsing.dart b/lib/app/services/converters/campaign_action_parsing.dart
index 3e0a154e..727f6e20 100644
--- a/lib/app/services/converters/campaign_action_parsing.dart
+++ b/lib/app/services/converters/campaign_action_parsing.dart
@@ -14,8 +14,22 @@ extension CampaignActionParsing on CampaignAction {
     return model;
   }
 
+  DoorCreateModel getAsDoorCreate() {
+    var data = jsonDecode(serialized!) as Map<String, dynamic>;
+    var model = DoorCreateModel.fromJson(data.convertLatLongField());
+
+    return model;
+  }
+
+  DoorUpdateModel getAsDoorUpdate() {
+    var data = jsonDecode(serialized!) as Map<String, dynamic>;
+    var model = DoorUpdateModel.fromJson(data.updateIdField(poiId!).convertLatLongField());
+
+    return model;
+  }
+
   PosterListItemModel getPosterUpdateAsPosterListItem(DateTime originalCreatedAt) {
-    var updateModel = getAsPosterUpdate().transformToPosterDetailModel(poiId!);
+    var updateModel = getAsPosterUpdate().transformToPosterDetailModel();
     return PosterListItemModel(
       id: updateModel.id,
       thumbnailUrl: updateModel.thumbnailUrl,
@@ -30,7 +44,7 @@ extension CampaignActionParsing on CampaignAction {
   }
 
   PosterListItemModel getPosterCreateAsPosterListItem() {
-    var createModel = getAsPosterCreate().transformToPosterDetailModel(poiTempId);
+    var createModel = getAsPosterCreate().transformToPosterDetailModel(poiTempId.toString());
     return PosterListItemModel(
       id: createModel.id,
       thumbnailUrl: createModel.thumbnailUrl,
diff --git a/lib/app/services/converters/door_create_model_parsing.dart b/lib/app/services/converters/door_create_model_parsing.dart
new file mode 100644
index 00000000..234e7367
--- /dev/null
+++ b/lib/app/services/converters/door_create_model_parsing.dart
@@ -0,0 +1,23 @@
+part of '../converters.dart';
+
+extension DoorCreateModelParsing on DoorCreateModel {
+  DoorDetailModel transformToDoorDetailModel(String temporaryId) {
+    return DoorDetailModel(
+      id: temporaryId,
+      address: address,
+      closedDoors: closedDoors,
+      openedDoors: openedDoors,
+      location: location,
+      createdAt: '${DateTime.now().getAsLocalDateTimeString()}*', // should mark this as preliminary
+      isCached: true,
+    );
+  }
+
+  MarkerItemModel transformToVirtualMarkerItem(int temporaryId) {
+    return MarkerItemModel.virtual(
+      id: temporaryId,
+      status: PoiServiceType.door.name,
+      location: location,
+    );
+  }
+}
diff --git a/lib/app/services/converters/door_update_model_parsing.dart b/lib/app/services/converters/door_update_model_parsing.dart
new file mode 100644
index 00000000..c28ce7a5
--- /dev/null
+++ b/lib/app/services/converters/door_update_model_parsing.dart
@@ -0,0 +1,21 @@
+part of '../converters.dart';
+
+extension DoorUpdateModelParsing on DoorUpdateModel {
+  DoorDetailModel transformToDoorDetailModel() {
+    var newDoorDetail = oldDoorDetail.copyWith(
+      address: address,
+      closedDoors: closedDoors,
+      openedDoors: openedDoors,
+      isCached: true,
+    );
+    return newDoorDetail;
+  }
+
+  MarkerItemModel transformToVirtualMarkerItem() {
+    return MarkerItemModel.virtual(
+      id: int.parse(id),
+      status: PoiServiceType.door.name,
+      location: location,
+    );
+  }
+}
diff --git a/lib/app/services/converters/map_string_dynamic_converter.dart b/lib/app/services/converters/map_string_dynamic_converter.dart
index 839286d9..7e40a2b9 100644
--- a/lib/app/services/converters/map_string_dynamic_converter.dart
+++ b/lib/app/services/converters/map_string_dynamic_converter.dart
@@ -2,7 +2,9 @@ part of '../converters.dart';
 
 extension MapStringDynamicConverter on Map<String, dynamic> {
   Map<String, dynamic> convertLatLongField({String fieldName = 'location'}) {
-    this[fieldName] = (this[fieldName] as List<dynamic>).cast<double>();
+    if (containsKey(fieldName) && this[fieldName] is List<dynamic>) {
+      this[fieldName] = (this[fieldName] as List<dynamic>).cast<double>().toList();
+    }
     return this;
   }
 
diff --git a/lib/app/services/converters/poi_parsing.dart b/lib/app/services/converters/poi_parsing.dart
index 56b33fcb..6f8aafdc 100644
--- a/lib/app/services/converters/poi_parsing.dart
+++ b/lib/app/services/converters/poi_parsing.dart
@@ -22,6 +22,7 @@ extension PoiParsing on Poi {
       address: poi.address.transformToAddressModel(),
       openedDoors: poi.house!.countOpenedDoors.toInt(),
       closedDoors: poi.house!.countClosedDoors.toInt(),
+      location: poi.coords.transformToLatLng(),
       createdAt: poi.createdAt.getAsLocalDateTimeString(),
     );
   }
diff --git a/lib/app/services/converters/poi_service_type_parsing.dart b/lib/app/services/converters/poi_service_type_parsing.dart
index 1fcc0cdb..233804b1 100644
--- a/lib/app/services/converters/poi_service_type_parsing.dart
+++ b/lib/app/services/converters/poi_service_type_parsing.dart
@@ -46,4 +46,37 @@ extension PoiServiceTypeParsing on PoiServiceType {
         return typeName;
     }
   }
+
+  CampaignActionType getCacheDeleteAction() {
+    switch (this) {
+      case PoiServiceType.poster:
+        return CampaignActionType.deletePoster;
+      case PoiServiceType.door:
+        return CampaignActionType.deleteDoor;
+      case PoiServiceType.flyer:
+        return CampaignActionType.deleteFlyer;
+    }
+  }
+
+  CampaignActionType getCacheEditAction() {
+    switch (this) {
+      case PoiServiceType.poster:
+        return CampaignActionType.editPoster;
+      case PoiServiceType.door:
+        return CampaignActionType.editDoor;
+      case PoiServiceType.flyer:
+        return CampaignActionType.editFlyer;
+    }
+  }
+
+  CampaignActionType getCacheAddAction() {
+    switch (this) {
+      case PoiServiceType.poster:
+        return CampaignActionType.addPoster;
+      case PoiServiceType.door:
+        return CampaignActionType.addDoor;
+      case PoiServiceType.flyer:
+        return CampaignActionType.addFlyer;
+    }
+  }
 }
diff --git a/lib/app/services/converters/poster_create_model_parsing.dart b/lib/app/services/converters/poster_create_model_parsing.dart
index 7f2314a0..00e08c31 100644
--- a/lib/app/services/converters/poster_create_model_parsing.dart
+++ b/lib/app/services/converters/poster_create_model_parsing.dart
@@ -9,9 +9,9 @@ extension PosterCreateModelParsing on PosterCreateModel {
     );
   }
 
-  PosterDetailModel transformToPosterDetailModel(int temporaryId) {
+  PosterDetailModel transformToPosterDetailModel(String temporaryId) {
     return PosterDetailModel(
-      id: temporaryId.toString(),
+      id: temporaryId,
       status: PosterStatus.ok,
       address: address,
       thumbnailUrl: imageFileLocation,
diff --git a/lib/app/services/converters/poster_update_model_parsing.dart b/lib/app/services/converters/poster_update_model_parsing.dart
index 83243e7b..95d5e866 100644
--- a/lib/app/services/converters/poster_update_model_parsing.dart
+++ b/lib/app/services/converters/poster_update_model_parsing.dart
@@ -9,7 +9,7 @@ extension PosterUpdateModelParsing on PosterUpdateModel {
     );
   }
 
-  PosterDetailModel transformToPosterDetailModel(int temporaryId) {
+  PosterDetailModel transformToPosterDetailModel() {
     var newPosterDetail = oldPosterDetail.copyWith(
       status: status,
       address: address,
diff --git a/lib/app/services/gruene_api_campaigns_service.dart b/lib/app/services/gruene_api_campaigns_service.dart
index ddaa79c1..780227d9 100644
--- a/lib/app/services/gruene_api_campaigns_service.dart
+++ b/lib/app/services/gruene_api_campaigns_service.dart
@@ -4,15 +4,11 @@ import 'package:flutter/foundation.dart';
 import 'package:get_it/get_it.dart';
 import 'package:gruene_app/app/services/converters.dart';
 import 'package:gruene_app/app/services/enums.dart';
-import 'package:gruene_app/features/campaigns/models/doors/door_create_model.dart';
-import 'package:gruene_app/features/campaigns/models/doors/door_detail_model.dart';
-import 'package:gruene_app/features/campaigns/models/doors/door_update_model.dart';
 import 'package:gruene_app/features/campaigns/models/flyer/flyer_create_model.dart';
 import 'package:gruene_app/features/campaigns/models/flyer/flyer_detail_model.dart';
 import 'package:gruene_app/features/campaigns/models/flyer/flyer_update_model.dart';
 import 'package:gruene_app/features/campaigns/models/map_layer_model.dart';
 import 'package:gruene_app/features/campaigns/models/marker_item_model.dart';
-import 'package:gruene_app/features/campaigns/models/posters/poster_detail_model.dart';
 import 'package:gruene_app/features/campaigns/models/posters/poster_list_item_model.dart';
 import 'package:gruene_app/swagger_generated_code/gruene_api.swagger.dart';
 import 'package:maplibre_gl/maplibre_gl.dart';
@@ -46,22 +42,6 @@ class GrueneApiCampaignsService {
     return getPoisResult.body!.data.map((layerItem) => layerItem.transformToMapLayer()).toList();
   }
 
-  Future<MarkerItemModel> createNewDoor(DoorCreateModel newDoor) async {
-    final requestParam = CreatePoi(
-      coords: newDoor.location.transformToGeoJsonCoords(),
-      type: poiType.transformToApiCreateType(),
-      address: newDoor.address.transformToPoiAddress(),
-      house: PoiHouse(
-        countOpenedDoors: newDoor.openedDoors.toDouble(),
-        countClosedDoors: newDoor.closedDoors.toDouble(),
-      ),
-    );
-    // saving POI
-    final newPoiResponse = await grueneApi.v1CampaignsPoisPost(body: requestParam);
-
-    return newPoiResponse.body!.transformToMarkerItem();
-  }
-
   Future<MarkerItemModel> createNewFlyer(FlyerCreateModel newFlyer) async {
     final requestParam = CreatePoi(
       coords: newFlyer.location.transformToGeoJsonCoords(),
@@ -77,23 +57,11 @@ class GrueneApiCampaignsService {
     return newPoiResponse.body!.transformToMarkerItem();
   }
 
-  Future<PosterDetailModel> getPoiAsPosterDetail(String poiId) async {
-    return _getPoi(poiId, (p) => p.transformPoiToPosterDetail());
-  }
-
-  Future<PosterListItemModel> getPoiAsPosterListItem(String poiId) {
-    return _getPoi(poiId, (p) => p.transformToPosterListItem());
-  }
-
-  Future<DoorDetailModel> getPoiAsDoorDetail(String poiId) {
-    return _getPoi(poiId, (p) => p.transformPoiToDoorDetail());
-  }
-
   Future<FlyerDetailModel> getPoiAsFlyerDetail(String poiId) {
-    return _getPoi(poiId, (p) => p.transformPoiToFlyerDetail());
+    return getPoi(poiId, (p) => p.transformPoiToFlyerDetail());
   }
 
-  Future<T> _getPoi<T>(String poiId, T Function(Poi) transform) async {
+  Future<T> getPoi<T>(String poiId, T Function(Poi) transform) async {
     final poiResponse = await grueneApi.v1CampaignsPoisPoiIdGet(poiId: poiId);
     return transform(poiResponse.body!);
   }
@@ -103,19 +71,6 @@ class GrueneApiCampaignsService {
     final deletePoiResponse = await grueneApi.v1CampaignsPoisPoiIdDelete(poiId: poiId);
   }
 
-  Future<MarkerItemModel> updateDoor(DoorUpdateModel doorUpdate) async {
-    var dtoUpdate = UpdatePoi(
-      address: doorUpdate.address.transformToPoiAddress(),
-      house: PoiHouse(
-        countOpenedDoors: doorUpdate.openedDoors.toDouble(),
-        countClosedDoors: doorUpdate.closedDoors.toDouble(),
-      ),
-    );
-    var updatePoiResponse = await grueneApi.v1CampaignsPoisPoiIdPut(poiId: doorUpdate.id, body: dtoUpdate);
-
-    return updatePoiResponse.body!.transformToMarkerItem();
-  }
-
   Future<MarkerItemModel> updateFlyer(FlyerUpdateModel flyerUpdate) async {
     var dtoUpdate = UpdatePoi(
       address: flyerUpdate.address.transformToPoiAddress(),
diff --git a/lib/app/services/gruene_api_door_service.dart b/lib/app/services/gruene_api_door_service.dart
new file mode 100644
index 00000000..bb30084d
--- /dev/null
+++ b/lib/app/services/gruene_api_door_service.dart
@@ -0,0 +1,45 @@
+import 'package:gruene_app/app/services/converters.dart';
+import 'package:gruene_app/app/services/enums.dart';
+import 'package:gruene_app/app/services/gruene_api_campaigns_service.dart';
+import 'package:gruene_app/features/campaigns/models/doors/door_create_model.dart';
+import 'package:gruene_app/features/campaigns/models/doors/door_detail_model.dart';
+import 'package:gruene_app/features/campaigns/models/doors/door_update_model.dart';
+import 'package:gruene_app/features/campaigns/models/marker_item_model.dart';
+import 'package:gruene_app/swagger_generated_code/gruene_api.swagger.dart';
+
+class GrueneApiDoorService extends GrueneApiCampaignsService {
+  GrueneApiDoorService() : super(poiType: PoiServiceType.door);
+
+  Future<MarkerItemModel> createNewDoor(DoorCreateModel newDoor) async {
+    final requestParam = CreatePoi(
+      coords: newDoor.location.transformToGeoJsonCoords(),
+      type: poiType.transformToApiCreateType(),
+      address: newDoor.address.transformToPoiAddress(),
+      house: PoiHouse(
+        countOpenedDoors: newDoor.openedDoors.toDouble(),
+        countClosedDoors: newDoor.closedDoors.toDouble(),
+      ),
+    );
+    // saving POI
+    final newPoiResponse = await grueneApi.v1CampaignsPoisPost(body: requestParam);
+
+    return newPoiResponse.body!.transformToMarkerItem();
+  }
+
+  Future<MarkerItemModel> updateDoor(DoorUpdateModel doorUpdate) async {
+    var dtoUpdate = UpdatePoi(
+      address: doorUpdate.address.transformToPoiAddress(),
+      house: PoiHouse(
+        countOpenedDoors: doorUpdate.openedDoors.toDouble(),
+        countClosedDoors: doorUpdate.closedDoors.toDouble(),
+      ),
+    );
+    var updatePoiResponse = await grueneApi.v1CampaignsPoisPoiIdPut(poiId: doorUpdate.id, body: dtoUpdate);
+
+    return updatePoiResponse.body!.transformToMarkerItem();
+  }
+
+  Future<DoorDetailModel> getPoiAsDoorDetail(String poiId) {
+    return getPoi(poiId, (p) => p.transformPoiToDoorDetail());
+  }
+}
diff --git a/lib/app/services/gruene_api_poster_service.dart b/lib/app/services/gruene_api_poster_service.dart
index 83033a58..ce5a9129 100644
--- a/lib/app/services/gruene_api_poster_service.dart
+++ b/lib/app/services/gruene_api_poster_service.dart
@@ -6,6 +6,8 @@ import 'package:gruene_app/app/services/gruene_api_campaigns_service.dart';
 import 'package:gruene_app/features/campaigns/helper/file_cache_manager.dart';
 import 'package:gruene_app/features/campaigns/models/marker_item_model.dart';
 import 'package:gruene_app/features/campaigns/models/posters/poster_create_model.dart';
+import 'package:gruene_app/features/campaigns/models/posters/poster_detail_model.dart';
+import 'package:gruene_app/features/campaigns/models/posters/poster_list_item_model.dart';
 import 'package:gruene_app/features/campaigns/models/posters/poster_update_model.dart';
 import 'package:gruene_app/swagger_generated_code/gruene_api.swagger.dart';
 import 'package:http/http.dart' as http;
@@ -76,4 +78,12 @@ class GrueneApiPosterService extends GrueneApiCampaignsService {
     fileManager.deleteFile(imageFileLocation);
     return savePoiPhotoResponse;
   }
+
+  Future<PosterDetailModel> getPoiAsPosterDetail(String poiId) async {
+    return getPoi(poiId, (p) => p.transformPoiToPosterDetail());
+  }
+
+  Future<PosterListItemModel> getPoiAsPosterListItem(String poiId) {
+    return getPoi(poiId, (p) => p.transformToPosterListItem());
+  }
 }
diff --git a/lib/features/campaigns/helper/campaign_action_cache.dart b/lib/features/campaigns/helper/campaign_action_cache.dart
index 39b677ba..56a61cd8 100644
--- a/lib/features/campaigns/helper/campaign_action_cache.dart
+++ b/lib/features/campaigns/helper/campaign_action_cache.dart
@@ -4,9 +4,14 @@ import 'package:flutter/material.dart';
 import 'package:get_it/get_it.dart';
 import 'package:gruene_app/app/services/campaign_action_database.dart';
 import 'package:gruene_app/app/services/converters.dart';
+import 'package:gruene_app/app/services/enums.dart';
+import 'package:gruene_app/app/services/gruene_api_door_service.dart';
 import 'package:gruene_app/app/services/gruene_api_poster_service.dart';
 import 'package:gruene_app/features/campaigns/helper/campaign_action.dart';
 import 'package:gruene_app/features/campaigns/helper/media_helper.dart';
+import 'package:gruene_app/features/campaigns/models/doors/door_create_model.dart';
+import 'package:gruene_app/features/campaigns/models/doors/door_detail_model.dart';
+import 'package:gruene_app/features/campaigns/models/doors/door_update_model.dart';
 import 'package:gruene_app/features/campaigns/models/marker_item_model.dart';
 import 'package:gruene_app/features/campaigns/models/posters/poster_create_model.dart';
 import 'package:gruene_app/features/campaigns/models/posters/poster_detail_model.dart';
@@ -29,7 +34,7 @@ class CampaignActionCache extends ChangeNotifier {
     return campaignActionDatabase.actionsWithPoiIdExists(poiId);
   }
 
-  Future<void> _addAction(CampaignAction action) async {
+  Future<void> _appendActionToCache(CampaignAction action) async {
     await campaignActionDatabase.create(action);
     notifyListeners();
   }
@@ -42,71 +47,127 @@ class CampaignActionCache extends ChangeNotifier {
     return campaignActionDatabase.getCount();
   }
 
-  Future<MarkerItemModel> addPosterCreate(PosterCreateModel posterCreate) async {
+  Future<MarkerItemModel> addCreateAction(PoiServiceType poiType, dynamic poiCreate) async {
+    switch (poiType) {
+      case PoiServiceType.poster:
+        return await _addCreateAction<PosterCreateModel>(
+          poiType: poiType,
+          poi: poiCreate as PosterCreateModel,
+          getJson: (poi) => poi.toJson(),
+          getMarker: (poi, tempId) => poi.transformToVirtualMarkerItem(tempId),
+        );
+      case PoiServiceType.door:
+        return await _addCreateAction<DoorCreateModel>(
+          poiType: poiType,
+          poi: poiCreate as DoorCreateModel,
+          getJson: (poi) => poi.toJson(),
+          getMarker: (poi, tempId) => poi.transformToVirtualMarkerItem(tempId),
+        );
+      case PoiServiceType.flyer:
+        throw UnimplementedError();
+    }
+  }
+
+  Future<MarkerItemModel> _addCreateAction<T>({
+    required PoiServiceType poiType,
+    required T poi,
+    required Map<String, dynamic> Function(T) getJson,
+    required MarkerItemModel Function(T, int) getMarker,
+  }) async {
     final action = CampaignAction(
-      actionType: CampaignActionType.addPoster,
-      serialized: jsonEncode(posterCreate.toJson()),
+      actionType: poiType.getCacheAddAction(),
+      serialized: jsonEncode(getJson(poi)),
     );
-    await _addAction(action);
-    return posterCreate.transformToVirtualMarkerItem(action.poiTempId);
+    await _appendActionToCache(action);
+    return getMarker(poi, action.poiTempId);
   }
 
-  Future<MarkerItemModel> addPosterDelete(String posterId) async {
+  Future<MarkerItemModel> addDeleteAction(PoiServiceType poiType, String poiId) async {
     final action = CampaignAction(
-      poiId: int.parse(posterId),
-      actionType: CampaignActionType.deletePoster,
+      poiId: int.parse(poiId),
+      actionType: poiType.getCacheDeleteAction(),
     );
 
-    var posterCacheList = await _findActionsByPoiId(posterId);
-    var addPosterActions = posterCacheList.where((p) => p.actionType == CampaignActionType.addPoster).toList();
-    if (addPosterActions.isNotEmpty) {
+    var poiCacheList = await _findActionsByPoiId(poiId);
+    var addActions = poiCacheList.where((p) => p.actionType == poiType.getCacheAddAction()).toList();
+    if (addActions.isNotEmpty) {
       // create_action is in cache
-      for (var action in posterCacheList) {
+      for (var action in poiCacheList) {
         campaignActionDatabase.delete(action.id!);
       }
       notifyListeners();
     } else {
-      await _addAction(action);
+      await _appendActionToCache(action);
     }
-    return _getDeletePosterMarkerModel(action.poiId!);
+    return _getDeleteMarkerModel(poiType, action.poiId!);
   }
 
-  Future<MarkerItemModel> addPosterUpdate(PosterUpdateModel posterUpdate) async {
-    var actions =
-        (await _findActionsByPoiId(posterUpdate.id)).where((x) => x.actionType == CampaignActionType.editPoster);
+  Future<MarkerItemModel> addUpdateAction(PoiServiceType poiType, dynamic poi) async {
+    switch (poiType) {
+      case PoiServiceType.poster:
+        return await _addUpdateAction<PosterUpdateModel>(
+          poiType: poiType,
+          poi: poi as PosterUpdateModel,
+          getId: (poi) => poi.id,
+          getJson: (poi) => poi.toJson(),
+          mergeUpdates: (action, poiUpdate) => action.getAsPosterUpdate().mergeWith(poiUpdate),
+          getMarker: (poi) => poi.transformToVirtualMarkerItem(),
+        );
+      case PoiServiceType.door:
+        return await _addUpdateAction<DoorUpdateModel>(
+          poiType: poiType,
+          poi: poi as DoorUpdateModel,
+          getId: (poi) => poi.id,
+          getJson: (poi) => poi.toJson(),
+          mergeUpdates: (action, poiUpdate) => poiUpdate,
+          getMarker: (poi) => poi.transformToVirtualMarkerItem(),
+        );
+      case PoiServiceType.flyer:
+        throw UnimplementedError();
+    }
+  }
+
+  Future<MarkerItemModel> _addUpdateAction<T>({
+    required PoiServiceType poiType,
+    required T poi,
+    required String Function(T) getId,
+    required Map<String, dynamic> Function(T) getJson,
+    required T Function(CampaignAction, T) mergeUpdates,
+    required MarkerItemModel Function(T) getMarker,
+  }) async {
+    var actions = (await _findActionsByPoiId(getId(poi))).where((x) => x.actionType == poiType.getCacheEditAction());
     var action = actions.singleOrNull;
     if (action == null) {
       action = CampaignAction(
-        poiId: int.parse(posterUpdate.id),
-        actionType: CampaignActionType.editPoster,
-        serialized: jsonEncode(posterUpdate.toJson()),
+        poiId: int.parse(getId(poi)),
+        actionType: poiType.getCacheEditAction(),
+        serialized: jsonEncode(getJson(poi)),
       );
-      await _addAction(action);
+      await _appendActionToCache(action);
     } else {
       // update previous edit action
-      var oldUpdate = action.getAsPosterUpdate();
-      var newPosterUpdate = oldUpdate.mergeWith(posterUpdate);
-      action.serialized = jsonEncode(newPosterUpdate.toJson());
+      var newPoiUpdate = mergeUpdates(action, poi);
+      action.serialized = jsonEncode(getJson(newPoiUpdate));
       await _updateAction(action);
     }
 
-    return posterUpdate.transformToVirtualMarkerItem();
+    return getMarker(poi);
   }
 
-  MarkerItemModel _getDeletePosterMarkerModel(int id) {
+  MarkerItemModel _getDeleteMarkerModel(PoiServiceType poiType, int id) {
     return MarkerItemModel.virtual(
       id: id,
-      status: 'poster_deleted',
+      status: '${poiType.name}_deleted',
       location: LatLng(0, 0),
     );
   }
 
-  Future<List<MarkerItemModel>> getPosterMarkerItems() async {
+  Future<List<MarkerItemModel>> getMarkerItems(PoiServiceType poiType) async {
     List<MarkerItemModel> markerItems = [];
     var posterActions = [
-      CampaignActionType.addPoster.index,
-      CampaignActionType.editPoster.index,
-      CampaignActionType.deletePoster.index,
+      poiType.getCacheAddAction().index,
+      poiType.getCacheEditAction().index,
+      poiType.getCacheDeleteAction().index,
     ];
     final posterCacheList = await campaignActionDatabase.readAllByActionType(posterActions);
     for (var action in posterCacheList) {
@@ -119,15 +180,23 @@ class CampaignActionCache extends ChangeNotifier {
           var model = action.getAsPosterUpdate();
           markerItems.add(model.transformToVirtualMarkerItem());
         case CampaignActionType.deletePoster:
-          var model = _getDeletePosterMarkerModel(action.poiId!);
+          var model = _getDeleteMarkerModel(PoiServiceType.poster, action.poiId!);
           markerItems.add(model);
-        case CampaignActionType.unknown:
         case CampaignActionType.addDoor:
+          var model = action.getAsDoorCreate();
+          markerItems.add(model.transformToVirtualMarkerItem(action.poiTempId));
         case CampaignActionType.editDoor:
+          var model = action.getAsDoorUpdate();
+          markerItems.add(model.transformToVirtualMarkerItem());
         case CampaignActionType.deleteDoor:
+          var model = _getDeleteMarkerModel(PoiServiceType.door, action.poiId!);
+          markerItems.add(model);
         case CampaignActionType.addFlyer:
         case CampaignActionType.editFlyer:
         case CampaignActionType.deleteFlyer:
+        // var model = _getDeleteMarkerModel(PoiServiceType.door, action.poiId!);
+        // markerItems.add(model);
+        case CampaignActionType.unknown:
         case null:
           throw UnimplementedError();
       }
@@ -137,18 +206,43 @@ class CampaignActionCache extends ChangeNotifier {
   }
 
   Future<PosterDetailModel> getPoiAsPosterDetail(String poiId) async {
-    var posterCacheList = await _findActionsByPoiId(poiId);
-    var addPosterActions = posterCacheList.where((p) => p.actionType == CampaignActionType.addPoster).toList();
-    var editPosterActions = posterCacheList.where((p) => p.actionType == CampaignActionType.editPoster).toList();
-    if (editPosterActions.isNotEmpty) {
-      var editPosterAction = editPosterActions.single;
-      var model = editPosterAction.getAsPosterUpdate();
-      return model.transformToPosterDetailModel(int.parse(poiId));
-    } else {
-      var addPosterAction = addPosterActions.single;
+    var detailModel = await getPoiDetail<PosterDetailModel>(
+      poiId: poiId,
+      addActionFilter: CampaignActionType.addDoor,
+      editActionFilter: CampaignActionType.editDoor,
+      transformEditAction: (action) => action.getAsPosterUpdate().transformToPosterDetailModel(),
+      transformAddAction: (action) => action.getAsPosterCreate().transformToPosterDetailModel(poiId),
+    );
+    return detailModel;
+  }
 
-      var model = addPosterAction.getAsPosterCreate();
-      return model.transformToPosterDetailModel(int.parse(poiId));
+  Future<DoorDetailModel> getPoiAsDoorDetail(String poiId) async {
+    var detailModel = await getPoiDetail<DoorDetailModel>(
+      poiId: poiId,
+      addActionFilter: CampaignActionType.addDoor,
+      editActionFilter: CampaignActionType.editDoor,
+      transformEditAction: (action) => action.getAsDoorUpdate().transformToDoorDetailModel(),
+      transformAddAction: (action) => action.getAsDoorCreate().transformToDoorDetailModel(poiId),
+    );
+    return detailModel;
+  }
+
+  Future<T> getPoiDetail<T>({
+    required String poiId,
+    required CampaignActionType addActionFilter,
+    required CampaignActionType editActionFilter,
+    required T Function(CampaignAction) transformEditAction,
+    required T Function(CampaignAction) transformAddAction,
+  }) async {
+    var cacheList = await _findActionsByPoiId(poiId);
+    var addActions = cacheList.where((p) => p.actionType == addActionFilter).toList();
+    var editActions = cacheList.where((p) => p.actionType == editActionFilter).toList();
+    if (editActions.isNotEmpty) {
+      var editAction = editActions.single;
+      return transformEditAction(editAction);
+    } else {
+      var addAction = addActions.single;
+      return transformAddAction(addAction);
     }
   }
 
@@ -160,6 +254,7 @@ class CampaignActionCache extends ChangeNotifier {
   void flushCachedItems() async {
     try {
       var posterApiService = GetIt.I<GrueneApiPosterService>();
+      var doorApiService = GetIt.I<GrueneApiDoorService>();
       final allActions = await campaignActionDatabase.readAll();
 
       for (int i = 0; i < allActions.length; i++) {
@@ -175,16 +270,33 @@ class CampaignActionCache extends ChangeNotifier {
               allActions: allActions,
             );
             campaignActionDatabase.delete(action.id!);
+
           case CampaignActionType.editPoster:
             var model = action.getAsPosterUpdate();
             await posterApiService.updatePoster(model);
             campaignActionDatabase.delete(action.id!);
-          case CampaignActionType.deletePoster:
-            await posterApiService.deletePoi(action.poiId!.toString());
-            campaignActionDatabase.delete(action.id!);
+
           case CampaignActionType.addDoor:
+            var model = action.getAsDoorCreate();
+            var newDoorMarker = await doorApiService.createNewDoor(model);
+            await updateIds(
+              oldId: action.poiTempId,
+              newId: newDoorMarker.id!,
+              startIndex: i + 1,
+              allActions: allActions,
+            );
+            campaignActionDatabase.delete(action.id!);
+
           case CampaignActionType.editDoor:
+            var model = action.getAsDoorUpdate();
+            await doorApiService.updateDoor(model);
+            campaignActionDatabase.delete(action.id!);
+
           case CampaignActionType.deleteDoor:
+          case CampaignActionType.deletePoster:
+            await posterApiService.deletePoi(action.poiId!.toString());
+            campaignActionDatabase.delete(action.id!);
+
           case CampaignActionType.addFlyer:
           case CampaignActionType.editFlyer:
           case CampaignActionType.deleteFlyer:
diff --git a/lib/features/campaigns/models/doors/door_create_model.dart b/lib/features/campaigns/models/doors/door_create_model.dart
index 1cce9bea..53898941 100644
--- a/lib/features/campaigns/models/doors/door_create_model.dart
+++ b/lib/features/campaigns/models/doors/door_create_model.dart
@@ -1,7 +1,13 @@
 import 'package:gruene_app/app/services/nominatim_service.dart';
+import 'package:gruene_app/features/campaigns/models/posters/poster_create_model.dart';
+import 'package:json_annotation/json_annotation.dart';
 import 'package:maplibre_gl/maplibre_gl.dart';
 
+part 'door_create_model.g.dart';
+
+@JsonSerializable()
 class DoorCreateModel {
+  @LatLongConverter()
   final LatLng location;
   final AddressModel address;
   final int openedDoors;
@@ -13,4 +19,8 @@ class DoorCreateModel {
     required this.openedDoors,
     required this.closedDoors,
   });
+
+  factory DoorCreateModel.fromJson(Map<String, dynamic> json) => _$DoorCreateModelFromJson(json);
+
+  Map<String, dynamic> toJson() => _$DoorCreateModelToJson(this);
 }
diff --git a/lib/features/campaigns/models/doors/door_detail_model.dart b/lib/features/campaigns/models/doors/door_detail_model.dart
index ca4bfe0e..ad15754a 100644
--- a/lib/features/campaigns/models/doors/door_detail_model.dart
+++ b/lib/features/campaigns/models/doors/door_detail_model.dart
@@ -1,5 +1,13 @@
+// ignore_for_file: public_member_api_docs, sort_constructors_first
+import 'package:gruene_app/app/services/converters.dart';
 import 'package:gruene_app/app/services/nominatim_service.dart';
+import 'package:gruene_app/features/campaigns/models/posters/poster_create_model.dart';
+import 'package:json_annotation/json_annotation.dart';
+import 'package:maplibre_gl/maplibre_gl.dart';
 
+part 'door_detail_model.g.dart';
+
+@JsonSerializable()
 class DoorDetailModel {
   String id;
 
@@ -7,6 +15,9 @@ class DoorDetailModel {
   final int openedDoors;
   final int closedDoors;
   final String createdAt;
+  @LatLongConverter()
+  final LatLng location;
+  final bool isCached;
 
   DoorDetailModel({
     required this.id,
@@ -14,5 +25,31 @@ class DoorDetailModel {
     required this.openedDoors,
     required this.closedDoors,
     required this.createdAt,
+    required this.location,
+    this.isCached = false,
   });
+
+  factory DoorDetailModel.fromJson(Map<String, dynamic> json) => _$DoorDetailModelFromJson(json.convertLatLongField());
+
+  Map<String, dynamic> toJson() => _$DoorDetailModelToJson(this);
+
+  DoorDetailModel copyWith({
+    String? id,
+    AddressModel? address,
+    int? openedDoors,
+    int? closedDoors,
+    String? createdAt,
+    LatLng? location,
+    bool? isCached,
+  }) {
+    return DoorDetailModel(
+      id: id ?? this.id,
+      address: address ?? this.address,
+      openedDoors: openedDoors ?? this.openedDoors,
+      closedDoors: closedDoors ?? this.closedDoors,
+      createdAt: createdAt ?? this.createdAt,
+      location: location ?? this.location,
+      isCached: isCached ?? this.isCached,
+    );
+  }
 }
diff --git a/lib/features/campaigns/models/doors/door_update_model.dart b/lib/features/campaigns/models/doors/door_update_model.dart
index 1b3e712f..b6bf5c6d 100644
--- a/lib/features/campaigns/models/doors/door_update_model.dart
+++ b/lib/features/campaigns/models/doors/door_update_model.dart
@@ -1,15 +1,31 @@
 import 'package:gruene_app/app/services/nominatim_service.dart';
+import 'package:gruene_app/features/campaigns/models/doors/door_detail_model.dart';
+import 'package:gruene_app/features/campaigns/models/posters/poster_create_model.dart';
+import 'package:json_annotation/json_annotation.dart';
+import 'package:maplibre_gl/maplibre_gl.dart';
 
+part 'door_update_model.g.dart';
+
+@JsonSerializable()
 class DoorUpdateModel {
-  int openedDoors;
-  int closedDoors;
-  String id;
-  AddressModel address;
+  final int openedDoors;
+  final int closedDoors;
+  final String id;
+  final AddressModel address;
+  @LatLongConverter()
+  final LatLng location;
+  final DoorDetailModel oldDoorDetail;
 
   DoorUpdateModel({
     required this.id,
     required this.address,
     required this.openedDoors,
     required this.closedDoors,
+    required this.oldDoorDetail,
+    required this.location,
   });
+
+  factory DoorUpdateModel.fromJson(Map<String, dynamic> json) => _$DoorUpdateModelFromJson(json);
+
+  Map<String, dynamic> toJson() => _$DoorUpdateModelToJson(this);
 }
diff --git a/lib/features/campaigns/models/flyer/flyer_detail_model.dart b/lib/features/campaigns/models/flyer/flyer_detail_model.dart
index 5b5fb644..68e23ffa 100644
--- a/lib/features/campaigns/models/flyer/flyer_detail_model.dart
+++ b/lib/features/campaigns/models/flyer/flyer_detail_model.dart
@@ -2,17 +2,16 @@ import 'package:gruene_app/app/services/nominatim_service.dart';
 
 class FlyerDetailModel {
   final String id;
-
   final AddressModel address;
-
   final int flyerCount;
-
   final String createdAt;
+  final bool isCached;
 
   FlyerDetailModel({
     required this.id,
     required this.address,
     required this.flyerCount,
     required this.createdAt,
+    this.isCached = false,
   });
 }
diff --git a/lib/features/campaigns/screens/door_edit.dart b/lib/features/campaigns/screens/door_edit.dart
index 2c2174c3..ae854214 100644
--- a/lib/features/campaigns/screens/door_edit.dart
+++ b/lib/features/campaigns/screens/door_edit.dart
@@ -153,6 +153,8 @@ class _DoorEditState extends State<DoorEdit> with AddressExtension, DoorValidato
       address: getAddress(),
       openedDoors: validationResult.openedDoors,
       closedDoors: validationResult.closedDoors,
+      location: widget.door.location,
+      oldDoorDetail: widget.door,
     );
     await widget.onSave(updateModel);
     _closeDialog();
diff --git a/lib/features/campaigns/screens/doors_screen.dart b/lib/features/campaigns/screens/doors_screen.dart
index 59952db3..137703d8 100644
--- a/lib/features/campaigns/screens/doors_screen.dart
+++ b/lib/features/campaigns/screens/doors_screen.dart
@@ -1,10 +1,12 @@
 import 'dart:math';
 
 import 'package:flutter/material.dart';
+import 'package:get_it/get_it.dart';
 import 'package:gruene_app/app/services/enums.dart';
-import 'package:gruene_app/app/services/gruene_api_campaigns_service.dart';
+import 'package:gruene_app/app/services/gruene_api_door_service.dart';
 import 'package:gruene_app/app/services/nominatim_service.dart';
 import 'package:gruene_app/features/campaigns/helper/campaign_constants.dart';
+import 'package:gruene_app/features/campaigns/helper/map_helper.dart';
 import 'package:gruene_app/features/campaigns/models/doors/door_create_model.dart';
 import 'package:gruene_app/features/campaigns/models/doors/door_detail_model.dart';
 import 'package:gruene_app/features/campaigns/models/doors/door_update_model.dart';
@@ -26,6 +28,7 @@ class DoorsScreen extends StatefulWidget {
 }
 
 class _DoorsScreenState extends MapConsumer<DoorsScreen> {
+  static const _poiType = PoiServiceType.door;
   final Map<String, List<String>> doorsExclusions = <String, List<String>>{
     t.campaigns.filters.focusAreas: [t.campaigns.filters.visited_areas],
     t.campaigns.filters.visited_areas: [t.campaigns.filters.focusAreas],
@@ -33,12 +36,12 @@ class _DoorsScreenState extends MapConsumer<DoorsScreen> {
 
   late List<FilterChipModel> doorsFilter;
 
-  final GrueneApiCampaignsService _grueneApiService = GrueneApiCampaignsService(poiType: PoiServiceType.door);
+  final _grueneApiService = GetIt.I<GrueneApiDoorService>();
 
-  _DoorsScreenState() : super(NominatimService());
+  _DoorsScreenState() : super(NominatimService(), _poiType);
 
   @override
-  GrueneApiCampaignsService get campaignService => _grueneApiService;
+  GrueneApiDoorService get campaignService => _grueneApiService;
 
   @override
   void initState() {
@@ -70,7 +73,7 @@ class _DoorsScreenState extends MapConsumer<DoorsScreen> {
       onMapCreated: onMapCreated,
       addPOIClicked: _addPOIClicked,
       loadVisibleItems: loadVisibleItems,
-      loadCachedItems: _loadCachedItems,
+      loadCachedItems: loadCachedItems,
       getMarkerImages: _getMarkerImages,
       onFeatureClick: _onFeatureClick,
       onNoFeatureClick: _onNoFeatureClick,
@@ -105,11 +108,21 @@ class _DoorsScreenState extends MapConsumer<DoorsScreen> {
   }
 
   void _onFeatureClick(dynamic rawFeature) async {
+    final feature = rawFeature as Map<String, dynamic>;
+    final isCached = MapHelper.extractIsCachedFromFeature(feature);
+
     getPoi(String poiId) async {
       final door = await campaignService.getPoiAsDoorDetail(poiId);
       return door;
     }
 
+    getCachedPoi(String poiId) async {
+      final door = await campaignActionCache.getPoiAsDoorDetail(poiId);
+      return door;
+    }
+
+    var getPoiFromCacheOrApi = isCached ? getCachedPoi : getPoi;
+
     getPoiDetail(DoorDetailModel door) {
       return DoorsDetail(
         poi: door,
@@ -122,7 +135,7 @@ class _DoorsScreenState extends MapConsumer<DoorsScreen> {
 
     super.onFeatureClick<DoorDetailModel>(
       rawFeature,
-      getPoi,
+      getPoiFromCacheOrApi,
       getPoiDetail,
       getEditPoiWidget,
       desiredSize: Size(145, 110),
@@ -134,7 +147,7 @@ class _DoorsScreenState extends MapConsumer<DoorsScreen> {
   }
 
   Future<void> _saveDoor(DoorUpdateModel doorUpdate) async {
-    final updatedMarker = await campaignService.updateDoor(doorUpdate);
+    final updatedMarker = await campaignActionCache.addUpdateAction(_poiType, doorUpdate);
     mapController.setMarkerSource([updatedMarker]);
   }
 
@@ -146,7 +159,5 @@ class _DoorsScreenState extends MapConsumer<DoorsScreen> {
   }
 
   Future<MarkerItemModel> _saveNewAndGetMarkerItem(DoorCreateModel newDoor) async =>
-      await _grueneApiService.createNewDoor(newDoor);
-
-  void _loadCachedItems() {}
+      await campaignActionCache.addCreateAction(_poiType, newDoor);
 }
diff --git a/lib/features/campaigns/screens/flyer_screen.dart b/lib/features/campaigns/screens/flyer_screen.dart
index 76181ee8..6cdae708 100644
--- a/lib/features/campaigns/screens/flyer_screen.dart
+++ b/lib/features/campaigns/screens/flyer_screen.dart
@@ -26,11 +26,12 @@ class FlyerScreen extends StatefulWidget {
 }
 
 class _FlyerScreenState extends MapConsumer<FlyerScreen> {
-  final GrueneApiCampaignsService _grueneApiService = GrueneApiCampaignsService(poiType: PoiServiceType.flyer);
+  static const _poiType = PoiServiceType.door;
+  final GrueneApiCampaignsService _grueneApiService = GrueneApiCampaignsService(poiType: _poiType);
 
   late List<FilterChipModel> flyerFilter;
 
-  _FlyerScreenState() : super(NominatimService());
+  _FlyerScreenState() : super(NominatimService(), _poiType);
 
   @override
   void initState() {
@@ -62,7 +63,7 @@ class _FlyerScreenState extends MapConsumer<FlyerScreen> {
       onMapCreated: onMapCreated,
       addPOIClicked: _addPOIClicked,
       loadVisibleItems: loadVisibleItems,
-      loadCachedItems: _loadCachedItems,
+      loadCachedItems: loadCachedItems,
       getMarkerImages: _getMarkerImages,
       onFeatureClick: _onFeatureClick,
       onNoFeatureClick: _onNoFeatureClick,
@@ -143,6 +144,4 @@ class _FlyerScreenState extends MapConsumer<FlyerScreen> {
     final updatedMarker = await campaignService.updateFlyer(flyerUpdate);
     mapController.setMarkerSource([updatedMarker]);
   }
-
-  void _loadCachedItems() {}
 }
diff --git a/lib/features/campaigns/screens/map_consumer.dart b/lib/features/campaigns/screens/map_consumer.dart
index fea94672..37311b3a 100644
--- a/lib/features/campaigns/screens/map_consumer.dart
+++ b/lib/features/campaigns/screens/map_consumer.dart
@@ -1,10 +1,13 @@
 import 'dart:math';
 
 import 'package:flutter/material.dart';
+import 'package:get_it/get_it.dart';
 import 'package:go_router/go_router.dart';
+import 'package:gruene_app/app/services/enums.dart';
 import 'package:gruene_app/app/services/gruene_api_campaigns_service.dart';
 import 'package:gruene_app/app/services/nominatim_service.dart';
 import 'package:gruene_app/app/theme/theme.dart';
+import 'package:gruene_app/features/campaigns/helper/campaign_action_cache.dart';
 import 'package:gruene_app/features/campaigns/helper/campaign_constants.dart';
 import 'package:gruene_app/features/campaigns/helper/enums.dart';
 import 'package:gruene_app/features/campaigns/helper/map_helper.dart';
@@ -24,7 +27,7 @@ typedef SaveNewAndGetMarkerCallback<T> = Future<MarkerItemModel> Function(T);
 typedef GetPoiCallback<T> = Future<T> Function(String);
 typedef GetPoiDetailWidgetCallback<T> = Widget Function(T);
 typedef GetPoiEditWidgetCallback<T> = Widget Function(T);
-typedef OnDeletePoiCallback = Future<void> Function(String posterId);
+typedef OnDeletePoiCallback = Future<void> Function(String poiId);
 
 abstract class MapConsumer<T extends StatefulWidget> extends State<T> with FocusAreaInfo {
   late MapController mapController;
@@ -35,8 +38,10 @@ abstract class MapConsumer<T extends StatefulWidget> extends State<T> with Focus
   final _minZoomFocusAreaLayer = 11.0;
   ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? _lastInfoSnackBar;
   String? _lastFocusAreaId;
+  final campaignActionCache = GetIt.I<CampaignActionCache>();
+  final PoiServiceType poiType;
 
-  MapConsumer(this._nominatimService);
+  MapConsumer(this._nominatimService, this.poiType);
 
   GrueneApiCampaignsService get campaignService;
 
@@ -147,9 +152,8 @@ abstract class MapConsumer<T extends StatefulWidget> extends State<T> with Focus
   }
 
   Future<void> deletePoi(String poiId) async {
-    final id = int.parse(poiId);
-    await campaignService.deletePoi(poiId);
-    mapController.removeMarkerItem(id);
+    var markerItem = await campaignActionCache.addDeleteAction(poiType, poiId);
+    mapController.setMarkerSource([markerItem]);
   }
 
   void addMapLayersForContext(MapLibreMapController mapLibreController) async {
@@ -326,4 +330,9 @@ abstract class MapConsumer<T extends StatefulWidget> extends State<T> with Focus
 
     toast.show(context);
   }
+
+  void loadCachedItems() async {
+    var markerItems = await campaignActionCache.getMarkerItems(poiType);
+    mapController.setMarkerSource(markerItems);
+  }
 }
diff --git a/lib/features/campaigns/screens/posters_screen.dart b/lib/features/campaigns/screens/posters_screen.dart
index 892e9bbb..ca1ab81c 100644
--- a/lib/features/campaigns/screens/posters_screen.dart
+++ b/lib/features/campaigns/screens/posters_screen.dart
@@ -4,10 +4,9 @@ import 'dart:math';
 import 'package:flutter/material.dart';
 import 'package:get_it/get_it.dart';
 import 'package:gruene_app/app/services/enums.dart';
-import 'package:gruene_app/app/services/gruene_api_campaigns_service.dart';
+import 'package:gruene_app/app/services/gruene_api_poster_service.dart';
 import 'package:gruene_app/app/services/nominatim_service.dart';
 import 'package:gruene_app/app/theme/theme.dart';
-import 'package:gruene_app/features/campaigns/helper/campaign_action_cache.dart';
 import 'package:gruene_app/features/campaigns/helper/campaign_constants.dart';
 import 'package:gruene_app/features/campaigns/helper/map_helper.dart';
 import 'package:gruene_app/features/campaigns/helper/media_helper.dart';
@@ -36,15 +35,15 @@ class PostersScreen extends StatefulWidget {
 }
 
 class _PostersScreenState extends MapConsumer<PostersScreen> {
-  final GrueneApiCampaignsService _grueneApiService = GrueneApiCampaignsService(poiType: PoiServiceType.poster);
-  final campaignActionCache = GetIt.I<CampaignActionCache>();
+  static const _poiType = PoiServiceType.poster;
+  final _grueneApiService = GetIt.I<GrueneApiPosterService>();
 
   late List<FilterChipModel> postersFilter;
 
-  _PostersScreenState() : super(NominatimService());
+  _PostersScreenState() : super(NominatimService(), _poiType);
 
   @override
-  GrueneApiCampaignsService get campaignService => _grueneApiService;
+  GrueneApiPosterService get campaignService => _grueneApiService;
 
   @override
   void initState() {
@@ -77,7 +76,7 @@ class _PostersScreenState extends MapConsumer<PostersScreen> {
       onMapCreated: onMapCreated,
       addPOIClicked: _addPOIClicked,
       loadVisibleItems: loadVisibleItems,
-      loadCachedItems: _loadCachedItems,
+      loadCachedItems: loadCachedItems,
       getMarkerImages: _getMarkerImages,
       onFeatureClick: _onFeatureClick,
       onNoFeatureClick: _onNoFeatureClick,
@@ -141,7 +140,7 @@ class _PostersScreenState extends MapConsumer<PostersScreen> {
   }
 
   Future<MarkerItemModel> saveNewAndGetMarkerItem(PosterCreateModel newPoster) async {
-    return await campaignActionCache.addPosterCreate(newPoster);
+    return await campaignActionCache.addCreateAction(_poiType, newPoster);
   }
 
   void _addPOIClicked(LatLng location) async {
@@ -181,7 +180,7 @@ class _PostersScreenState extends MapConsumer<PostersScreen> {
   }
 
   Widget _getEditPosterWidget(PosterDetailModel poster) {
-    return PosterEdit(poster: poster, onSave: _savePoster, onDelete: _deletePoster);
+    return PosterEdit(poster: poster, onSave: _savePoster, onDelete: deletePoi);
   }
 
   void _onFeatureClick(dynamic rawFeature) async {
@@ -210,7 +209,7 @@ class _PostersScreenState extends MapConsumer<PostersScreen> {
   }
 
   Future<void> _savePoster(PosterUpdateModel posterUpdate) async {
-    final updatedMarker = await campaignActionCache.addPosterUpdate(posterUpdate);
+    final updatedMarker = await campaignActionCache.addUpdateAction(_poiType, posterUpdate);
     mapController.setMarkerSource([updatedMarker]);
   }
 
@@ -247,16 +246,6 @@ class _PostersScreenState extends MapConsumer<PostersScreen> {
     return myPosters;
   }
 
-  void _loadCachedItems() async {
-    var markerItems = await campaignActionCache.getPosterMarkerItems();
-    mapController.setMarkerSource(markerItems);
-  }
-
-  Future<void> _deletePoster(String posterId) async {
-    var markerItem = await campaignActionCache.addPosterDelete(posterId);
-    mapController.setMarkerSource([markerItem]);
-  }
-
   Future<PosterListItemModel> _getPosterListItem(String id) async {
     return await campaignActionCache.isCached(id)
         ? campaignActionCache.getPoiAsPosterListItem(id)
diff --git a/lib/main.dart b/lib/main.dart
index 61004e53..7ded799e 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -9,6 +9,7 @@ import 'package:gruene_app/app/auth/bloc/auth_bloc.dart';
 import 'package:gruene_app/app/auth/repository/auth_repository.dart';
 import 'package:gruene_app/app/router.dart';
 import 'package:gruene_app/app/services/gruene_api_core.dart';
+import 'package:gruene_app/app/services/gruene_api_door_service.dart';
 import 'package:gruene_app/app/services/gruene_api_poster_service.dart';
 import 'package:gruene_app/app/theme/theme.dart';
 import 'package:gruene_app/app/widgets/clean_layout.dart';
@@ -41,13 +42,12 @@ Future<void> main() async {
   GetIt.I.registerSingleton<GrueneApi>(await createGrueneApiClient());
   GetIt.I.registerSingleton<CampaignSessionSettings>(CampaignSessionSettings());
   GetIt.I.registerSingleton<CampaignActionCache>(CampaignActionCache());
-  // GetIt.I.registerSingleton<CacheManager>(FileCacheManager.instance);
   GetIt.I.registerSingleton<FileManager>(FileManager());
 
   GetIt.I.registerFactory<AuthenticatorService>(MfaFactory.create);
   GetIt.I.registerFactory<GrueneApiPosterService>(() => GrueneApiPosterService());
-  // This is required so ObjectBox can get the application directory
-  // to store the database in.
+  GetIt.I.registerFactory<GrueneApiDoorService>(() => GrueneApiDoorService());
+
   WidgetsFlutterBinding.ensureInitialized();
 
   runApp(TranslationProvider(child: const MyApp()));