diff --git a/bus_terminal/devtools_options.yaml b/bus_terminal/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/bus_terminal/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/bus_terminal/lib/models/user.dart b/bus_terminal/lib/models/user.dart index 676c26e..3d0481c 100644 --- a/bus_terminal/lib/models/user.dart +++ b/bus_terminal/lib/models/user.dart @@ -1,7 +1,7 @@ /// User model class User { final int id; - final String userCode; + final int userCode; final String? job; User({ @@ -11,9 +11,20 @@ class User { }); factory User.fromJson(Map json) { + // userCode may be provided as 'userCode' or 'usercode' and may be int or string + final dynamic rawUserCode = json['userCode'] ?? json['usercode'] ?? json['user_code']; + int parsedUserCode; + if (rawUserCode is int) { + parsedUserCode = rawUserCode; + } else if (rawUserCode is String) { + parsedUserCode = int.tryParse(rawUserCode) ?? (throw FormatException('Invalid userCode')); + } else { + throw FormatException('Missing userCode'); + } + return User( id: json['id'] as int, - userCode: json['userCode'] as String, + userCode: parsedUserCode, job: json['job'] as String?, ); } @@ -30,17 +41,44 @@ class User { /// Login response model class AuthResponse { final String token; - final User user; + final User? user; AuthResponse({ required this.token, - required this.user, + this.user, }); factory AuthResponse.fromJson(Map json) { + final dynamic rawToken = json['token'] ?? json['Token'] ?? json['authToken']; + final token = rawToken?.toString(); + if (token == null || token.isEmpty) throw FormatException('Missing token in auth response'); + + // Determine if a user object exists and appears valid + Map? userMap; + if (json.containsKey('user') && json['user'] is Map) { + userMap = json['user'] as Map; + } else { + // maybe user fields are top-level (id, userCode, job) + final hasUserFields = json.containsKey('id') || json.containsKey('userCode') || json.containsKey('usercode') || json.containsKey('user_code'); + if (hasUserFields) { + // treat whole json as user map + userMap = json.cast(); + } + } + + User? parsedUser; + if (userMap != null) { + try { + parsedUser = User.fromJson(userMap); + } catch (e) { + // If parsing user fails, ignore user but keep token + parsedUser = null; + } + } + return AuthResponse( - token: json['token'] as String, - user: User.fromJson(json['user'] as Map), + token: token, + user: parsedUser, ); } } diff --git a/bus_terminal/lib/screens/login_screen.dart b/bus_terminal/lib/screens/login_screen.dart index d3643dc..5808210 100644 --- a/bus_terminal/lib/screens/login_screen.dart +++ b/bus_terminal/lib/screens/login_screen.dart @@ -34,8 +34,14 @@ class _LoginScreenState extends State { }); try { + final input = _userCodeController.text.trim(); + final parsedUserCode = int.tryParse(input); + if (parsedUserCode == null) { + throw Exception('User code must be a number'); + } + final response = await _authService.login( - userCode: _userCodeController.text.trim(), + userCode: parsedUserCode, password: _passwordController.text, ); @@ -57,9 +63,13 @@ class _LoginScreenState extends State { } String? _validateUserCode(String? value) { - if (value?.isEmpty ?? true) { + final v = value?.trim(); + if (v?.isEmpty ?? true) { return 'User code is required'; } + if (int.tryParse(v!) == null) { + return 'User code must be a number'; + } return null; } diff --git a/bus_terminal/lib/services/auth_service.dart b/bus_terminal/lib/services/auth_service.dart index 51b3c98..9358b40 100644 --- a/bus_terminal/lib/services/auth_service.dart +++ b/bus_terminal/lib/services/auth_service.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:ffi'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; import '../models/user.dart'; @@ -6,7 +7,7 @@ import '../models/user.dart'; /// Authentication Service class AuthService { // TODO: Implement API connector - configure base URL from env/config - static const String _apiBaseUrl = 'http://localhost:5000'; + static const String _apiBaseUrl = 'http://192.168.2.66:5000'; static const String _tokenKey = 'auth_token'; late SharedPreferences _prefs; @@ -17,7 +18,7 @@ class AuthService { /// Login with userCode and password Future login({ - required String userCode, + required int userCode, required String password, }) async { await _initPrefs(); @@ -33,13 +34,19 @@ class AuthService { ); if (response.statusCode == 200) { - final data = jsonDecode(response.body) as Map; - final authResponse = AuthResponse.fromJson(data); + final body = response.body; + try { + final data = jsonDecode(body) as Map; + final authResponse = AuthResponse.fromJson(data); - // Store token - await _prefs.setString(_tokenKey, authResponse.token); + // Store token + await _prefs.setString(_tokenKey, authResponse.token); - return authResponse; + return authResponse; + } catch (e) { + // Surface the response body to help debug backend field names / formats + throw Exception('Login parse error: $e -- response body: $body'); + } } else if (response.statusCode == 401) { throw Exception('Invalid credentials'); } else { diff --git a/bus_terminal/lib/services/schedule_service.dart b/bus_terminal/lib/services/schedule_service.dart index b09d57c..e8b3634 100644 --- a/bus_terminal/lib/services/schedule_service.dart +++ b/bus_terminal/lib/services/schedule_service.dart @@ -6,7 +6,8 @@ import 'auth_service.dart'; /// Schedule/Route Service class ScheduleService { // TODO: Implement API connector - configure base URL from env/config - static const String _apiBaseUrl = 'http://localhost:5000'; + // Transit API runs on port 5001 + static const String _apiBaseUrl = 'http://192.168.2.66:5001'; final _authService = AuthService(); /// Get daily schedule for a specific date @@ -43,8 +44,9 @@ class ScheduleService { try { final headers = await _authService.getAuthHeaders(); + // Transit API exposes route details at /routes/:routeId final response = await http.get( - Uri.parse('$_apiBaseUrl/schedule/route/$routeId'), + Uri.parse('$_apiBaseUrl/routes/$routeId'), headers: headers, ); @@ -66,8 +68,9 @@ class ScheduleService { try { final headers = await _authService.getAuthHeaders(); + // Transit API updates status at /routes/:routeId/status final response = await http.patch( - Uri.parse('$_apiBaseUrl/schedule/route/$routeId/status'), + Uri.parse('$_apiBaseUrl/routes/$routeId/status'), headers: headers, body: jsonEncode({'status': status}), ); diff --git a/docker-compose.yml b/docker-compose.yml index ecfffa5..326425d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,51 +2,45 @@ version: '3.9' services: auth_api: - image: ghcr.io/Sven491/XTransit/auth_api:latest + image: ghcr.io/sven491/xtransit/auth_api:latest container_name: auth_api ports: - "5000:5000" environment: PORT: 5000 JWT_SECRET: ${JWT_SECRET} - DB_HOST: ${POSTGRES_HOST} - DB_PORT: ${POSTGRES_PORT} - DB_NAME: ${POSTGRES_DB} - DB_USER: ${POSTGRES_USER_AUTH} - DB_PASSWORD: ${POSTGRES_PASSWORD_AUTH} - depends_on: - db: - condition: service_healthy + PG_HOST: ${POSTGRES_HOST} + PG_PORT: ${POSTGRES_PORT} + PG_DB: ${POSTGRES_DB} + PG_USER: ${POSTGRES_USER_AUTH} + PG_PASS: ${POSTGRES_PASSWORD_AUTH} restart: unless-stopped employee_api: - image: ghcr.io/Sven491/XTransit/employee_api:latest + image: ghcr.io/sven491/xtransit/employee_api:latest container_name: employee_api ports: - "4000:4000" environment: PORT: 4000 - DB_HOST: ${POSTGRES_HOST} - DB_PORT: ${POSTGRES_PORT} - DB_NAME: ${POSTGRES_DB} - DB_USER: ${POSTGRES_USER_EMPLOYEE} - DB_PASSWORD: ${POSTGRES_PASSWORD_EMPLOYEE} - depends_on: - db: - condition: service_healthy + PG_HOST: ${POSTGRES_HOST} + PG_PORT: ${POSTGRES_PORT} + PG_DB: ${POSTGRES_DB} + PG_USER: ${POSTGRES_USER_EMPLOYEE} + PG_PASS: ${POSTGRES_PASSWORD_EMPLOYEE} restart: unless-stopped transit_api: - image: ghcr.io/Sven491/XTransit/transit_api:latest + image: ghcr.io/sven491/xtransit/transit_api:latest container_name: transit_api ports: - "5001:5001" environment: PORT: 5001 JWT_SECRET: ${JWT_SECRET} - DB_HOST: ${POSTGRES_HOST} - DB_PORT: ${POSTGRES_PORT} - DB_NAME: ${POSTGRES_DB} - DB_USER: ${POSTGRES_USER_TRANSIT} - DB_PASSWORD: ${POSTGRES_PASSWORD_TRANSIT} - restart: unless-stopped + PG_HOST: ${POSTGRES_HOST} + PG_PORT: ${POSTGRES_PORT} + PG_DB: ${POSTGRES_DB} + PG_USER: ${POSTGRES_USER_TRANSIT} + PG_PASS: ${POSTGRES_PASSWORD_TRANSIT} + restart: unless-stopped \ No newline at end of file diff --git a/transit_api/index.js b/transit_api/index.js index 97a6cd7..f621209 100644 --- a/transit_api/index.js +++ b/transit_api/index.js @@ -37,7 +37,7 @@ app.get('/schedule/daily', authMiddleware, async (req, res) => { bt.name as bus_type, bt.seat_capacity, bt.license_plate - FROM routes r + FROM transit.routes r JOIN bus_lines bl ON r.bus_line_id = bl.id JOIN bus_types bt ON r.bus_type_id = bt.id WHERE r.user_id = $1 AND DATE(r.start_time) = $2