diff --git a/assets/badges/badge_bronze.svg b/assets/badges/badge_bronze.svg
new file mode 100644
index 00000000..e953ac01
--- /dev/null
+++ b/assets/badges/badge_bronze.svg
@@ -0,0 +1,132 @@
+
+
diff --git a/assets/badges/badge_empty.svg b/assets/badges/badge_empty.svg
new file mode 100644
index 00000000..051414ab
--- /dev/null
+++ b/assets/badges/badge_empty.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/badges/badge_gold.svg b/assets/badges/badge_gold.svg
new file mode 100644
index 00000000..2dc66e9b
--- /dev/null
+++ b/assets/badges/badge_gold.svg
@@ -0,0 +1,178 @@
+
+
diff --git a/assets/badges/badge_platinum.svg b/assets/badges/badge_platinum.svg
new file mode 100644
index 00000000..12131cbc
--- /dev/null
+++ b/assets/badges/badge_platinum.svg
@@ -0,0 +1,178 @@
+
+
diff --git a/assets/badges/badge_silver.svg b/assets/badges/badge_silver.svg
new file mode 100644
index 00000000..21673012
--- /dev/null
+++ b/assets/badges/badge_silver.svg
@@ -0,0 +1,178 @@
+
+
diff --git a/lib/app/services/converters.dart b/lib/app/services/converters.dart
index 6f7b7936..714afa38 100644
--- a/lib/app/services/converters.dart
+++ b/lib/app/services/converters.dart
@@ -28,3 +28,4 @@ part 'converters/poi_parsing.dart';
part 'converters/slider_range_parsing.dart';
part 'converters/place_parser.dart';
part 'converters/string_extension.dart';
+part 'converters/date_time_parsing.dart';
diff --git a/lib/app/services/converters/date_time_parsing.dart b/lib/app/services/converters/date_time_parsing.dart
new file mode 100644
index 00000000..f6ca8f6f
--- /dev/null
+++ b/lib/app/services/converters/date_time_parsing.dart
@@ -0,0 +1,13 @@
+part of '../converters.dart';
+
+extension DateTimeParsing on DateTime {
+ String getAsLocalDateTimeString() {
+ DateTime utcDateTime = this;
+ DateTime localDateTime = utcDateTime.toLocal();
+ final dateString = DateFormat(t.campaigns.poster.date_format).format(localDateTime);
+ final timeString = DateFormat(t.campaigns.poster.time_format).format(localDateTime);
+ return t.campaigns.poster.datetime_display_template
+ .replaceAll('{date}', dateString)
+ .replaceAll('{time}', timeString);
+ }
+}
diff --git a/lib/app/services/gruene_api_campaigns_statistics_service.dart b/lib/app/services/gruene_api_campaigns_statistics_service.dart
new file mode 100644
index 00000000..768d246f
--- /dev/null
+++ b/lib/app/services/gruene_api_campaigns_statistics_service.dart
@@ -0,0 +1,45 @@
+import 'package:flutter/material.dart';
+import 'package:get_it/get_it.dart';
+import 'package:gruene_app/features/campaigns/models/statistics/campaign_statistics.dart';
+import 'package:gruene_app/features/campaigns/models/statistics/campaign_statistics_set.dart';
+import 'package:gruene_app/swagger_generated_code/gruene_api.swagger.dart';
+
+class GrueneApiCampaignsStatisticsService {
+ late GrueneApi grueneApi;
+
+ GrueneApiCampaignsStatisticsService() {
+ grueneApi = GetIt.I();
+ }
+
+ Future getStatistics() async {
+ try {
+ var statResult = await grueneApi.v1CampaignsStatisticsGet();
+ return statResult.body!.asCampaignStatistics();
+ } catch (e, s) {
+ debugPrint(e.toString());
+ debugPrint(s.toString());
+ rethrow;
+ }
+ }
+}
+
+extension StatisticsParser on Statistics {
+ CampaignStatistics asCampaignStatistics() {
+ return CampaignStatistics(
+ flyerStats: flyer.asStatisticsSet(),
+ houseStats: house.asStatisticsSet(),
+ posterStats: poster.asStatisticsSet(),
+ );
+ }
+}
+
+extension PoiStatisticsParser on PoiStatistics {
+ CampaignStatisticsSet asStatisticsSet() {
+ return CampaignStatisticsSet(
+ own: own,
+ division: division,
+ state: state,
+ germany: germany,
+ );
+ }
+}
diff --git a/lib/app/theme/theme.dart b/lib/app/theme/theme.dart
index 2909987c..9beff5e7 100644
--- a/lib/app/theme/theme.dart
+++ b/lib/app/theme/theme.dart
@@ -8,7 +8,7 @@ class ThemeColors {
// Secondary Light Green (#008939)
static const Color secondary = Color(0xFF008939);
- // Secondary Light Green (#008939)
+ // Tertiary Light Green (#46962B)
static const Color tertiary = Color(0xFF46962B);
// White (#FFFFFF)
diff --git a/lib/features/campaigns/helper/campaign_session_settings.dart b/lib/features/campaigns/helper/campaign_session_settings.dart
index 0658dc42..5d4bbf1a 100644
--- a/lib/features/campaigns/helper/campaign_session_settings.dart
+++ b/lib/features/campaigns/helper/campaign_session_settings.dart
@@ -1,10 +1,14 @@
import 'package:gruene_app/app/services/nominatim_service.dart';
+import 'package:gruene_app/features/campaigns/models/statistics/campaign_statistics.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
class CampaignSessionSettings {
LatLng? lastPosition;
double? lastZoomLevel;
+ CampaignStatistics? recentStatistics;
+ DateTime? recentStatisticsFetchTimestamp;
+
bool imageConsentConfirmed = false;
String? searchString;
diff --git a/lib/features/campaigns/models/statistics/campaign_statistics.dart b/lib/features/campaigns/models/statistics/campaign_statistics.dart
new file mode 100644
index 00000000..766cb4b4
--- /dev/null
+++ b/lib/features/campaigns/models/statistics/campaign_statistics.dart
@@ -0,0 +1,11 @@
+import 'package:gruene_app/features/campaigns/models/statistics/campaign_statistics_set.dart';
+
+class CampaignStatistics {
+ final CampaignStatisticsSet flyerStats, houseStats, posterStats;
+
+ const CampaignStatistics({
+ required this.flyerStats,
+ required this.houseStats,
+ required this.posterStats,
+ });
+}
diff --git a/lib/features/campaigns/models/statistics/campaign_statistics_set.dart b/lib/features/campaigns/models/statistics/campaign_statistics_set.dart
new file mode 100644
index 00000000..e4258364
--- /dev/null
+++ b/lib/features/campaigns/models/statistics/campaign_statistics_set.dart
@@ -0,0 +1,10 @@
+class CampaignStatisticsSet {
+ final double own, division, state, germany;
+
+ const CampaignStatisticsSet({
+ required this.own,
+ required this.division,
+ required this.state,
+ required this.germany,
+ });
+}
diff --git a/lib/features/campaigns/screens/campaigns_screen.dart b/lib/features/campaigns/screens/campaigns_screen.dart
index 94e333e3..3bacade4 100644
--- a/lib/features/campaigns/screens/campaigns_screen.dart
+++ b/lib/features/campaigns/screens/campaigns_screen.dart
@@ -19,7 +19,7 @@ class _CampaignsScreen extends State with SingleTickerProviderS
CampaignMenuModel(t.campaigns.poster.label, true, PostersScreen()),
CampaignMenuModel(t.campaigns.flyer.label, true, FlyerScreen()),
CampaignMenuModel(t.campaigns.team.label, false, TeamsScreen()),
- CampaignMenuModel(t.campaigns.statistic.label, false, StatisticsScreen()),
+ CampaignMenuModel(t.campaigns.statistic.label, true, StatisticsScreen()),
];
late TabController _tabController;
diff --git a/lib/features/campaigns/screens/statistics_screen.dart b/lib/features/campaigns/screens/statistics_screen.dart
index 83ecc3cb..836c3619 100644
--- a/lib/features/campaigns/screens/statistics_screen.dart
+++ b/lib/features/campaigns/screens/statistics_screen.dart
@@ -1,20 +1,287 @@
import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:get_it/get_it.dart';
+import 'package:gruene_app/app/services/converters.dart';
+import 'package:gruene_app/app/services/gruene_api_campaigns_statistics_service.dart';
import 'package:gruene_app/app/theme/theme.dart';
+import 'package:gruene_app/features/campaigns/helper/app_settings.dart';
+import 'package:gruene_app/features/campaigns/helper/campaign_constants.dart';
+import 'package:gruene_app/features/campaigns/models/statistics/campaign_statistics.dart';
+import 'package:gruene_app/features/campaigns/models/statistics/campaign_statistics_set.dart';
import 'package:gruene_app/i18n/translations.g.dart';
+import 'package:intl/intl.dart';
class StatisticsScreen extends StatelessWidget {
const StatisticsScreen({super.key});
@override
Widget build(BuildContext context) {
- return Placeholder(
- color: Colors.red,
- child: Center(
- child: Text(
- t.campaigns.statistic.label,
- style: TextStyle(fontSize: 20, color: ThemeColors.primary),
+ final theme = Theme.of(context);
+
+ return FutureBuilder(
+ future: _loadStatistics(),
+ builder: (context, snapshot) {
+ if (!snapshot.hasData || snapshot.hasError) {
+ return Image.asset(CampaignConstants.dummyImageAssetName);
+ }
+ return _buildStatScreen(snapshot.data!, theme, context);
+ },
+ );
+ }
+
+ SingleChildScrollView _buildStatScreen(CampaignStatistics statistics, ThemeData theme, BuildContext context) {
+ var lastUpdateTime = GetIt.I().campaign.recentStatisticsFetchTimestamp ?? DateTime.now();
+ return SingleChildScrollView(
+ child: Container(
+ padding: EdgeInsets.all(16),
+ color: theme.colorScheme.surfaceDim,
+ child: Column(
+ children: [
+ _getBadgeBox(statistics, context, theme),
+ SizedBox(height: 12),
+ _getCategoryBox(
+ stats: statistics.houseStats,
+ theme: theme,
+ title: t.campaigns.statistic.recorded_doors,
+ ),
+ SizedBox(height: 12),
+ _getCategoryBox(
+ stats: statistics.posterStats,
+ theme: theme,
+ title: t.campaigns.statistic.recorded_posters,
+ subTitle: t.campaigns.statistic.including_damaged_or_taken_down,
+ ),
+ SizedBox(height: 12),
+ _getCategoryBox(
+ stats: statistics.flyerStats,
+ theme: theme,
+ title: t.campaigns.statistic.recorded_flyer,
+ ),
+ Container(
+ padding: EdgeInsets.all(16),
+ child: Align(
+ alignment: Alignment.centerLeft,
+ child: Text(
+ '${t.campaigns.statistic.as_at}: ${lastUpdateTime.getAsLocalDateTimeString()} (${t.campaigns.statistic.update_info})',
+ style: theme.textTheme.labelMedium!.apply(color: ThemeColors.textDisabled),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _getBadgeBox(CampaignStatistics statistics, BuildContext context, ThemeData theme) {
+ var mediaQuery = MediaQuery.of(context);
+ return Container(
+ padding: EdgeInsets.all(16),
+ width: mediaQuery.size.width,
+ decoration: BoxDecoration(
+ color: ThemeColors.background,
+ borderRadius: BorderRadius.circular(19),
+ boxShadow: [
+ BoxShadow(color: ThemeColors.textDark.withAlpha(10), offset: Offset(2, 4)),
+ ],
+ ),
+ child: Column(
+ children: [
+ Align(
+ alignment: Alignment.centerLeft,
+ child: Text(
+ t.campaigns.statistic.my_badges,
+ style: theme.textTheme.titleMedium,
+ ),
+ ),
+ Align(
+ alignment: Alignment.centerLeft,
+ child: Text(
+ t.campaigns.statistic.my_badges_campaign_subtitle,
+ style: theme.textTheme.labelSmall,
+ ),
+ ),
+ ..._getBadges(statistics, theme),
+ ],
+ ),
+ );
+ }
+
+ List _getBadges(CampaignStatistics statistics, ThemeData theme) {
+ return [
+ _getBadgeRow(t.campaigns.statistic.recorded_doors, statistics.houseStats.own.toInt(), theme),
+ _getBadgeRow(t.campaigns.statistic.recorded_posters, statistics.posterStats.own.toInt(), theme),
+ _getBadgeRow(t.campaigns.statistic.recorded_flyer, statistics.flyerStats.own.toInt(), theme),
+ ];
+ }
+
+ Widget _getBadgeRow(String title, int ownCounter, ThemeData theme) {
+ return Container(
+ decoration: BoxDecoration(
+ border: Border(
+ bottom: BorderSide(color: ThemeColors.textLight),
),
),
+ padding: EdgeInsets.all(4),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(title, style: theme.textTheme.labelLarge!.copyWith(color: ThemeColors.textDark)),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ ..._getBadgeIcons(ownCounter, theme),
+ ],
+ ),
+ ],
+ ),
);
}
+
+ List _getBadgeIcons(int value, ThemeData theme) {
+ var thresholds = [50, 100, 250, 500];
+ var badges = ['bronze', 'silver', 'gold', 'platinum'];
+ var widgets = [];
+ var iconSize = 50.0;
+ for (var i = 0; i < thresholds.length; i++) {
+ var currentThreshold = thresholds[i];
+ if (currentThreshold < value) {
+ widgets.add(
+ SizedBox(
+ height: iconSize,
+ child: Stack(
+ children: [
+ SvgPicture.asset(
+ 'assets/badges/badge_${badges[i]}.svg',
+ fit: BoxFit.fill,
+ height: iconSize,
+ width: iconSize,
+ ),
+ Positioned.fill(
+ child: Align(
+ alignment: Alignment.center,
+ child: Text(
+ currentThreshold.toString(),
+ style: theme.textTheme.labelMedium!.apply(fontWeightDelta: 3, fontStyle: FontStyle.italic),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ } else {
+ widgets.add(
+ SizedBox(
+ height: iconSize,
+ child: Stack(
+ children: [
+ SvgPicture.asset(
+ 'assets/badges/badge_empty.svg',
+ fit: BoxFit.fill,
+ height: iconSize,
+ width: iconSize,
+ ),
+ Positioned.fill(
+ child: Align(
+ alignment: Alignment.center,
+ child: Text(
+ currentThreshold.toString(),
+ style: theme.textTheme.labelMedium!
+ .apply(fontWeightDelta: 3, color: theme.colorScheme.primary.withOpacity(0.3)),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+ if (currentThreshold != thresholds.last) widgets.add(SizedBox(width: 5));
+ }
+ return widgets;
+ }
+
+ Widget _getCategoryBox({
+ required String title,
+ String? subTitle,
+ required ThemeData theme,
+ required CampaignStatisticsSet stats,
+ }) {
+ var categoryDecoration = BoxDecoration(
+ color: ThemeColors.background,
+ borderRadius: BorderRadius.circular(19),
+ boxShadow: [
+ BoxShadow(color: ThemeColors.textDark.withAlpha(10), offset: Offset(2, 4)),
+ ],
+ );
+ return Container(
+ padding: EdgeInsets.all(16),
+ decoration: categoryDecoration,
+ child: Column(
+ children: [
+ Row(
+ children: [
+ Text(
+ title,
+ style: theme.textTheme.titleMedium,
+ ),
+ ],
+ ),
+ subTitle != null
+ ? Row(
+ children: [
+ Text(
+ subTitle,
+ style: theme.textTheme.labelSmall!.copyWith(color: ThemeColors.textDisabled),
+ ),
+ ],
+ )
+ : SizedBox(),
+ _getDataRow(t.campaigns.statistic.by_me, stats.own.toInt(), theme),
+ _getDataRow(t.campaigns.statistic.by_my_KV, stats.division.toInt(), theme),
+ _getDataRow(t.campaigns.statistic.by_my_LV, stats.state.toInt(), theme),
+ _getDataRow(t.campaigns.statistic.in_germany, stats.germany.toInt(), theme),
+ ],
+ ),
+ );
+ }
+
+ Widget _getDataRow(String key, int value, ThemeData theme) {
+ var formatter = NumberFormat('#,##,##0', t.$meta.locale.languageCode);
+ return Container(
+ padding: const EdgeInsets.all(4),
+ decoration: BoxDecoration(
+ border: Border(
+ bottom: BorderSide(color: ThemeColors.textLight),
+ ),
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(key, style: theme.textTheme.labelLarge!.copyWith(color: ThemeColors.textDark)),
+ Text(
+ formatter.format(value),
+ style: theme.textTheme.labelLarge!.copyWith(color: ThemeColors.textDark),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Future _loadStatistics() async {
+ var campaignSettings = GetIt.I().campaign;
+
+ if (campaignSettings.recentStatistics != null &&
+ DateTime.now().isBefore(campaignSettings.recentStatisticsFetchTimestamp!.add(Duration(minutes: 5)))) {
+ return campaignSettings.recentStatistics!;
+ }
+ var statApiService = GetIt.I();
+ var campaignStatistics = await statApiService.getStatistics();
+
+ campaignSettings.recentStatistics = campaignStatistics;
+ campaignSettings.recentStatisticsFetchTimestamp = DateTime.now();
+
+ return campaignStatistics;
+ }
}
diff --git a/lib/i18n/app_de.json b/lib/i18n/app_de.json
index 7cf0db93..facd9b07 100644
--- a/lib/i18n/app_de.json
+++ b/lib/i18n/app_de.json
@@ -111,7 +111,19 @@
"label": "Team"
},
"statistic": {
- "label": "Statistik"
+ "label": "Statistik",
+ "my_badges": "Meine Abzeichen",
+ "my_badges_campaign_subtitle": "Bundestagswahl 2025",
+ "recorded_posters": "Plakate",
+ "including_damaged_or_taken_down": "inkl. beschädigt und abgehängt",
+ "recorded_doors": "Haustüren",
+ "recorded_flyer": "Flyer",
+ "by_me": "von mir erfasst",
+ "by_my_KV": "von meinen Kreisverband",
+ "by_my_LV": "von meinem Landesverband",
+ "in_germany": "deutschlandweit",
+ "update_info": "wird alle 5 Minuten aktualisiert",
+ "as_at": "Stand"
},
"address": {
"street": "Straße",
diff --git a/lib/main.dart b/lib/main.dart
index 10bafdc5..c27a5c0b 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -8,6 +8,7 @@ import 'package:get_it/get_it.dart';
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_campaigns_statistics_service.dart';
import 'package:gruene_app/app/services/gruene_api_core.dart';
import 'package:gruene_app/app/services/ip_service.dart';
import 'package:gruene_app/app/services/nominatim_service.dart';
@@ -39,6 +40,7 @@ void main() async {
}
registerSecureStorage();
+ GetIt.I.registerSingleton(GrueneApiCampaignsStatisticsService());
GetIt.I.registerSingleton(AppSettings());
GetIt.I.registerFactory(MfaFactory.create);
GetIt.I.registerSingleton(IpService());
diff --git a/pubspec.yaml b/pubspec.yaml
index fc923012..ec193a75 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -83,6 +83,7 @@ dev_dependencies:
flutter:
uses-material-design: true
assets:
+ - assets/badges/
- assets/bottom_navigation/
- assets/icons/
- assets/splash/
diff --git a/swaggers/gruene-api.yaml b/swaggers/gruene-api.yaml
index 41f4a8e2..6c767d53 100644
--- a/swaggers/gruene-api.yaml
+++ b/swaggers/gruene-api.yaml
@@ -1050,6 +1050,137 @@ paths:
security:
- bearer: []
- oauth2: []
+ /v1/campaigns/polling-stations:
+ post:
+ operationId: createPollingStation
+ summary: Create a new PollingStation
+ parameters: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreatePollingStation'
+ responses:
+ '201':
+ description: ''
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PollingStation'
+ '401':
+ description: ''
+ tags:
+ - campaigns
+ security:
+ - bearer: []
+ - oauth2: []
+ get:
+ operationId: findPollingStations
+ summary: Find PollingStations
+ parameters:
+ - name: bbox
+ required: false
+ in: query
+ schema:
+ example: 47.695103,7.659684,53.670793, 14.229507
+ type: string
+ responses:
+ '200':
+ description: ''
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/FindPollingStationsResponse'
+ '401':
+ description: ''
+ tags:
+ - campaigns
+ security:
+ - bearer: []
+ - oauth2: []
+ /v1/campaigns/polling-stations/{pollingStationId}:
+ get:
+ operationId: getPollingStation
+ summary: Get a PollingStation
+ parameters:
+ - name: pollingStationId
+ required: true
+ in: path
+ schema:
+ type: string
+ responses:
+ '200':
+ description: ''
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PollingStation'
+ '401':
+ description: ''
+ '404':
+ description: ''
+ tags:
+ - campaigns
+ security:
+ - bearer: []
+ - oauth2: []
+ put:
+ operationId: updatePollingStation
+ summary: Update a PollingStation
+ parameters:
+ - name: pollingStationId
+ required: true
+ in: path
+ schema:
+ type: string
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdatePollingStation'
+ responses:
+ '200':
+ description: ''
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PollingStation'
+ '401':
+ description: ''
+ '404':
+ description: ''
+ tags:
+ - campaigns
+ security:
+ - bearer: []
+ - oauth2: []
+ delete:
+ operationId: deletePollingStation
+ summary: Delete a PollingStation
+ parameters:
+ - name: pollingStationId
+ required: true
+ in: path
+ schema:
+ type: string
+ responses:
+ '200':
+ description: ''
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PollingStation'
+ '401':
+ description: ''
+ '404':
+ description: ''
+ tags:
+ - campaigns
+ security:
+ - bearer: []
+ - oauth2: []
/v1/campaigns/pois:
post:
operationId: createPoi
@@ -1295,6 +1426,148 @@ paths:
security:
- bearer: []
- oauth2: []
+ /v1/campaigns/routes:
+ post:
+ operationId: createRoute
+ summary: Create a new Route
+ parameters: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateRoute'
+ responses:
+ '201':
+ description: ''
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Route'
+ '401':
+ description: ''
+ tags:
+ - campaigns
+ security:
+ - bearer: []
+ - oauth2: []
+ get:
+ operationId: findRoutes
+ summary: Find Routes
+ parameters:
+ - name: type
+ required: false
+ in: query
+ description: filter by Route type
+ schema:
+ example: POSTER
+ enum:
+ - FLYER_SPOT
+ - POSTER
+ - HOUSE
+ type: string
+ - name: bbox
+ required: false
+ in: query
+ schema:
+ example: 47.695103,7.659684,53.670793, 14.229507
+ type: string
+ responses:
+ '200':
+ description: ''
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/FindRoutesResponse'
+ '401':
+ description: ''
+ tags:
+ - campaigns
+ security:
+ - bearer: []
+ - oauth2: []
+ /v1/campaigns/routes/{routeId}:
+ get:
+ operationId: getRoute
+ summary: Get a Route
+ parameters:
+ - name: routeId
+ required: true
+ in: path
+ schema:
+ type: string
+ responses:
+ '200':
+ description: ''
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Route'
+ '401':
+ description: ''
+ '404':
+ description: ''
+ tags:
+ - campaigns
+ security:
+ - bearer: []
+ - oauth2: []
+ put:
+ operationId: updateRoute
+ summary: Update a Route
+ parameters:
+ - name: routeId
+ required: true
+ in: path
+ schema:
+ type: string
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UpdateRoute'
+ responses:
+ '200':
+ description: ''
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Route'
+ '401':
+ description: ''
+ '404':
+ description: ''
+ tags:
+ - campaigns
+ security:
+ - bearer: []
+ - oauth2: []
+ delete:
+ operationId: deleteRoute
+ summary: Delete a Route
+ parameters:
+ - name: routeId
+ required: true
+ in: path
+ schema:
+ type: string
+ responses:
+ '200':
+ description: ''
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Route'
+ '401':
+ description: ''
+ '404':
+ description: ''
+ tags:
+ - campaigns
+ security:
+ - bearer: []
+ - oauth2: []
/v1/campaigns/experience-areas:
post:
operationId: createExperienceArea
@@ -1557,6 +1830,25 @@ paths:
security:
- bearer: []
- oauth2: []
+ /v1/campaigns/statistics:
+ get:
+ operationId: getStatistics
+ summary: Get statistics
+ parameters: []
+ responses:
+ '200':
+ description: ''
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Statistics'
+ '401':
+ description: ''
+ tags:
+ - campaigns
+ security:
+ - bearer: []
+ - oauth2: []
/v1/news:
get:
operationId: findNews
@@ -1676,7 +1968,7 @@ tags: []
servers:
- url: https://api.gruene.de
description: Production
- - url: http://192.168.178.95:5000
+ - url: http://192.168.178.35:5000
description: Development
components:
securitySchemes:
@@ -3043,6 +3335,84 @@ components:
- zip
- street
- houseNumber
+ UpdatePollingStation:
+ type: object
+ properties:
+ coords:
+ description: Coordinates represented in GeoJSON [longitude, latitude]
+ example:
+ - 52.5297
+ - 13.4266
+ minItems: 2
+ maxItems: 2
+ type: array
+ items:
+ type: number
+ address:
+ $ref: '#/components/schemas/PoiAddress'
+ required:
+ - coords
+ - address
+ CreatePollingStation:
+ type: object
+ properties:
+ coords:
+ description: Coordinates represented in GeoJSON [longitude, latitude]
+ example:
+ - 52.5297
+ - 13.4266
+ minItems: 2
+ maxItems: 2
+ type: array
+ items:
+ type: number
+ address:
+ $ref: '#/components/schemas/PoiAddress'
+ required:
+ - coords
+ - address
+ PollingStation:
+ type: object
+ properties:
+ coords:
+ description: Coordinates represented in GeoJSON [longitude, latitude]
+ example:
+ - 52.5297
+ - 13.4266
+ minItems: 2
+ maxItems: 2
+ type: array
+ items:
+ type: number
+ id:
+ type: string
+ example: '1'
+ userId:
+ type: string
+ createdAt:
+ format: date-time
+ type: string
+ updatedAt:
+ format: date-time
+ type: string
+ address:
+ $ref: '#/components/schemas/PoiAddress'
+ required:
+ - coords
+ - id
+ - userId
+ - createdAt
+ - updatedAt
+ - address
+ FindPollingStationsResponse:
+ type: object
+ properties:
+ data:
+ type: array
+ items:
+ $ref: '#/components/schemas/PollingStation'
+ required:
+ - data
PoiPoster:
type: object
properties:
@@ -3252,6 +3622,86 @@ components:
$ref: '#/components/schemas/Poi'
required:
- data
+ LineString:
+ type: object
+ properties:
+ type:
+ type: string
+ description: Type of the LineString
+ example: LineString
+ default: LineString
+ coordinates:
+ type: array
+ items:
+ required: true
+ description: |-
+ Coordinates of the LineString
+ Must follow the GeoJSON standard
+ type: array
+ items:
+ type: number
+ required:
+ - type
+ - coordinates
+ UpdateRoute:
+ type: object
+ properties:
+ type:
+ enum:
+ - FLYER_SPOT
+ - POSTER
+ - HOUSE
+ type: string
+ lineString:
+ $ref: '#/components/schemas/LineString'
+ required:
+ - type
+ - lineString
+ CreateRoute:
+ type: object
+ properties:
+ type:
+ enum:
+ - FLYER_SPOT
+ - POSTER
+ - HOUSE
+ type: string
+ lineString:
+ $ref: '#/components/schemas/LineString'
+ required:
+ - type
+ - lineString
+ Route:
+ type: object
+ properties:
+ id:
+ type: string
+ example: '1'
+ type:
+ enum:
+ - FLYER_SPOT
+ - POSTER
+ - HOUSE
+ type: string
+ createdAt:
+ format: date-time
+ type: string
+ lineString:
+ $ref: '#/components/schemas/LineString'
+ required:
+ - id
+ - type
+ - createdAt
+ - lineString
+ FindRoutesResponse:
+ type: object
+ properties:
+ data:
+ type: array
+ items:
+ $ref: '#/components/schemas/Route'
+ required:
+ - data
UpdateExperienceArea:
type: object
properties:
@@ -3385,6 +3835,35 @@ components:
$ref: '#/components/schemas/FocusArea'
required:
- data
+ PoiStatistics:
+ type: object
+ properties:
+ own:
+ type: number
+ division:
+ type: number
+ state:
+ type: number
+ germany:
+ type: number
+ required:
+ - own
+ - division
+ - state
+ - germany
+ Statistics:
+ type: object
+ properties:
+ poster:
+ $ref: '#/components/schemas/PoiStatistics'
+ flyer:
+ $ref: '#/components/schemas/PoiStatistics'
+ house:
+ $ref: '#/components/schemas/PoiStatistics'
+ required:
+ - poster
+ - flyer
+ - house
NewsCategory:
type: object
properties: