Update readme's, branches and actions
Build and Push Docker Images / detect-changes (push) Successful in 10s
Build and Push Docker Images / build-auth-api (push) Successful in 58s
Build and Push Docker Images / build-employee-api (push) Successful in 58s
Build and Push Docker Images / build-transit-api (push) Successful in 59s
Build and Push Docker Images / build-error-log-api (push) Successful in 1m8s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m14s
Build and Push Docker Images / deploy-test-node (push) Successful in 5s
Build and Push Docker Images / detect-changes (push) Successful in 10s
Build and Push Docker Images / build-auth-api (push) Successful in 58s
Build and Push Docker Images / build-employee-api (push) Successful in 58s
Build and Push Docker Images / build-transit-api (push) Successful in 59s
Build and Push Docker Images / build-error-log-api (push) Successful in 1m8s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m14s
Build and Push Docker Images / deploy-test-node (push) Successful in 5s
This commit is contained in:
@@ -0,0 +1,323 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import '../models/schedule.dart';
|
||||
import '../models/navigation.dart';
|
||||
import 'auth_service.dart';
|
||||
import 'error_log_service.dart';
|
||||
import 'exceptions.dart';
|
||||
|
||||
/// Schedule/Route Service
|
||||
class ScheduleService {
|
||||
static const String _apiBaseUrl = 'https://transit.xtransit.testinstance.nl';
|
||||
final _authService = AuthService();
|
||||
|
||||
Future<void> _reportError(String partOfService, Object error) {
|
||||
return ErrorLogService.report(
|
||||
service: 'bus_terminal',
|
||||
partOfService: partOfService,
|
||||
error: error.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Get public schedule overview for a specific date.
|
||||
Future<List<ServiceSchedule>> getSchedules(DateTime date, {int? lineId}) async {
|
||||
try {
|
||||
final dateStr = '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
final queryParameters = <String, String>{'date': dateStr};
|
||||
if (lineId != null) {
|
||||
queryParameters['lineId'] = lineId.toString();
|
||||
}
|
||||
|
||||
final uri = Uri.parse('$_apiBaseUrl/schedules/overview').replace(queryParameters: queryParameters);
|
||||
final response = await http.get(uri);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return (data['schedules'] as List? ?? [])
|
||||
.map((schedule) => schedule as Map<String, dynamic>)
|
||||
.map(ServiceSchedule.fromJson)
|
||||
.toList();
|
||||
}
|
||||
|
||||
throw Exception('Failed to load schedules: ${response.statusCode}');
|
||||
} catch (e) {
|
||||
await _reportError('ScheduleService.getSchedules', e);
|
||||
throw Exception('Schedule error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all recurring schedule templates, independent of a specific date.
|
||||
Future<List<ServiceSchedule>> getRecurringSchedules({int? lineId}) async {
|
||||
try {
|
||||
final queryParameters = <String, String>{};
|
||||
if (lineId != null) {
|
||||
queryParameters['lineId'] = lineId.toString();
|
||||
}
|
||||
|
||||
final uri = Uri.parse('$_apiBaseUrl/schedules/recurring').replace(queryParameters: queryParameters);
|
||||
final response = await http.get(uri);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return (data['schedules'] as List? ?? [])
|
||||
.map((schedule) => schedule as Map<String, dynamic>)
|
||||
.map(ServiceSchedule.fromJson)
|
||||
.toList();
|
||||
}
|
||||
|
||||
throw Exception('Failed to load recurring schedules: ${response.statusCode}');
|
||||
} catch (e) {
|
||||
await _reportError('ScheduleService.getRecurringSchedules', e);
|
||||
throw Exception('Recurring schedule error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get stop list for a specific schedule.
|
||||
Future<List<ScheduleStop>> getScheduleStops(int scheduleId) async {
|
||||
try {
|
||||
final response = await http.get(Uri.parse('$_apiBaseUrl/schedules/$scheduleId/stops'));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return (data['stops'] as List? ?? [])
|
||||
.map((stop) => stop as Map<String, dynamic>)
|
||||
.map(ScheduleStop.fromJson)
|
||||
.toList();
|
||||
}
|
||||
|
||||
throw Exception('Failed to load schedule stops: ${response.statusCode}');
|
||||
} catch (e) {
|
||||
await _reportError('ScheduleService.getScheduleStops', e);
|
||||
throw Exception('Schedule stops error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get daily schedule for a specific date (includes recurring schedules)
|
||||
Future<DailySchedule> getDailySchedule(DateTime date) async {
|
||||
try {
|
||||
final dateStr = '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
|
||||
// Call /schedules/overview which includes both one-time and recurring schedules for the date
|
||||
final uri = Uri.parse('$_apiBaseUrl/schedules/overview').replace(
|
||||
queryParameters: {'date': dateStr}
|
||||
);
|
||||
final response = await http.get(uri);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final schedules = (data['schedules'] as List? ?? [])
|
||||
.map((s) => s as Map<String, dynamic>)
|
||||
.map(ServiceSchedule.fromJson)
|
||||
.toList();
|
||||
|
||||
// Convert ServiceSchedule to Route objects for display
|
||||
final routes = schedules.map((schedule) {
|
||||
return Route(
|
||||
id: schedule.id,
|
||||
busLine: BusLine(
|
||||
id: schedule.busLineId,
|
||||
lineNumber: schedule.lineNumber,
|
||||
startStop: '',
|
||||
endStop: '',
|
||||
estimatedDuration: 60,
|
||||
description: '',
|
||||
),
|
||||
busType: BusType(
|
||||
id: schedule.busId,
|
||||
name: schedule.busName,
|
||||
seatCapacity: 0,
|
||||
licensePlate: '',
|
||||
),
|
||||
startTime: schedule.startTime,
|
||||
endTime: schedule.endTime,
|
||||
status: schedule.status,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return DailySchedule(
|
||||
routes: routes,
|
||||
date: date,
|
||||
);
|
||||
} else {
|
||||
throw Exception('Failed to load schedule: ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
await _reportError('ScheduleService.getDailySchedule', e);
|
||||
throw Exception('Schedule error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get today's schedule
|
||||
Future<DailySchedule> getTodaySchedule() async {
|
||||
return getDailySchedule(DateTime.now());
|
||||
}
|
||||
|
||||
/// Get per-date progress for a schedule occurrence (public endpoint)
|
||||
Future<Map<String, dynamic>> getScheduleProgress(int scheduleId, DateTime date) async {
|
||||
try {
|
||||
final dateStr = '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
final uri = Uri.parse('$_apiBaseUrl/public/schedules/$scheduleId/progress').replace(queryParameters: {'date': dateStr});
|
||||
final response = await http.get(uri);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return jsonDecode(response.body) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
throw Exception('Failed to load progress: ${response.statusCode}');
|
||||
} catch (e) {
|
||||
await _reportError('ScheduleService.getScheduleProgress', e);
|
||||
throw Exception('Schedule progress error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get route details
|
||||
Future<Route> getRouteDetails(int routeId) async {
|
||||
try {
|
||||
final headers = await _authService.getAuthHeaders();
|
||||
|
||||
final response = await http.get(
|
||||
Uri.parse('$_apiBaseUrl/routes/$routeId'),
|
||||
headers: headers,
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return Route.fromJson(data);
|
||||
} else if (response.statusCode == 401 || response.statusCode == 403) {
|
||||
throw UnauthorizedException('Session expired - Please login again');
|
||||
} else {
|
||||
final body = response.body;
|
||||
throw Exception('Failed to load route: ${response.statusCode} -- response body: $body');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is UnauthorizedException) rethrow;
|
||||
await _reportError('ScheduleService.getRouteDetails', e);
|
||||
throw Exception('Route error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Update route status
|
||||
Future<void> updateRouteStatus(int routeId, String status) async {
|
||||
try {
|
||||
final headers = await _authService.getAuthHeaders();
|
||||
|
||||
// Transit API updates status at /routes/:routeId/status
|
||||
final response = await http.patch(
|
||||
Uri.parse('$_apiBaseUrl/routes/$routeId/status'),
|
||||
headers: headers,
|
||||
body: jsonEncode({'status': status}),
|
||||
);
|
||||
|
||||
if (response.statusCode == 401 || response.statusCode == 403) {
|
||||
throw UnauthorizedException('Session expired - Please login again');
|
||||
} else if (response.statusCode != 200) {
|
||||
throw Exception('Failed to update status: ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is UnauthorizedException) rethrow;
|
||||
await _reportError('ScheduleService.updateRouteStatus', e);
|
||||
throw Exception('Status update error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Update schedule status for driver-facing schedule workflows.
|
||||
Future<void> updateScheduleStatus(int scheduleId, String status, {DateTime? date}) async {
|
||||
try {
|
||||
final headers = await _authService.getAuthHeaders();
|
||||
|
||||
final body = <String, dynamic>{'status': status};
|
||||
if (date != null) {
|
||||
final dateStr = '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
body['date'] = dateStr;
|
||||
}
|
||||
|
||||
final response = await http.patch(
|
||||
Uri.parse('$_apiBaseUrl/driver/schedules/$scheduleId/status'),
|
||||
headers: headers,
|
||||
body: jsonEncode(body),
|
||||
);
|
||||
|
||||
if (response.statusCode == 401 || response.statusCode == 403) {
|
||||
throw UnauthorizedException('Session expired - Please login again');
|
||||
} else if (response.statusCode != 200) {
|
||||
throw Exception('Failed to update schedule status: ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is UnauthorizedException) rethrow;
|
||||
await _reportError('ScheduleService.updateScheduleStatus', e);
|
||||
throw Exception('Schedule status update error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get stops for a bus line
|
||||
Future<List<NavigationPoint>> getStops(int busLineId) async {
|
||||
try {
|
||||
final headers = await _authService.getAuthHeaders();
|
||||
|
||||
final response = await http.get(
|
||||
Uri.parse('$_apiBaseUrl/bus-lines/$busLineId/stops'),
|
||||
headers: headers,
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final raw = data['stops'] as List? ?? [];
|
||||
final stops = raw.map((s) => s as Map<String, dynamic>).map((s) {
|
||||
final lat = (s['latitude'] ?? s['lat']) as num?;
|
||||
final lon = (s['longitude'] ?? s['lon']) as num?;
|
||||
final name = (s['name'] ?? s['stop_name'] ?? s['stopName']) as String?;
|
||||
if (lat == null || lon == null) return null;
|
||||
return NavigationPoint(
|
||||
latitude: lat.toDouble(),
|
||||
longitude: lon.toDouble(),
|
||||
name: name,
|
||||
);
|
||||
}).whereType<NavigationPoint>().toList();
|
||||
|
||||
return stops;
|
||||
} else if (response.statusCode == 401 || response.statusCode == 403) {
|
||||
throw UnauthorizedException('Session expired - Please login again');
|
||||
} else {
|
||||
final body = response.body;
|
||||
throw Exception('Failed to load stops: ${response.statusCode} -- response body: $body');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is UnauthorizedException) rethrow;
|
||||
await _reportError('ScheduleService.getStops', e);
|
||||
throw Exception('Get stops error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark a schedule stop as passed by the driver.
|
||||
Future<void> markScheduleStopPassed({
|
||||
required int scheduleId,
|
||||
required int stopOrder,
|
||||
DateTime? actualPassedAt,
|
||||
}) async {
|
||||
try {
|
||||
final headers = await _authService.getAuthHeaders();
|
||||
|
||||
final body = <String, dynamic>{};
|
||||
if (actualPassedAt != null) body['actualPassedAt'] = actualPassedAt.toIso8601String();
|
||||
|
||||
final response = await http.post(
|
||||
Uri.parse('$_apiBaseUrl/driver/schedules/$scheduleId/stops/$stopOrder/passed'),
|
||||
headers: headers,
|
||||
body: jsonEncode(body),
|
||||
);
|
||||
|
||||
if (response.statusCode == 401 || response.statusCode == 403) {
|
||||
await _authService.logout();
|
||||
throw UnauthorizedException('Session expired - Please login again');
|
||||
}
|
||||
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
throw Exception('Failed to mark stop as passed: ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is UnauthorizedException) rethrow;
|
||||
await _reportError('ScheduleService.markScheduleStopPassed', e);
|
||||
throw Exception('Mark stop passed error: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user