// lib/services/api_service.dart import 'package:get/get.dart'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:gifunavi/model/entry.dart'; import 'package:gifunavi/model/event.dart'; import 'package:gifunavi/model/team.dart'; import 'package:gifunavi/model/category.dart'; import 'package:gifunavi/model/user.dart'; import 'package:gifunavi/pages/index/index_controller.dart'; import '../routes/app_pages.dart'; import '../utils/const.dart'; import 'package:intl/intl.dart'; class ApiService extends GetxService{ static ApiService get to => Get.find(); String serverUrl = ''; String baseUrl = ''; String token = 'your-auth-token'; Future init() async { try { //if (!Get.isRegistered()) { // Get.put(IndexController(apiService: Get.find())); //} // ここで必要な初期化処理を行う serverUrl = ConstValues.currentServer(); baseUrl = '$serverUrl/api'; //await Future.delayed(Duration(seconds: 2)); // 仮の遅延(実際の初期化処理に置き換えてください) print('ApiService initialized successfully . baseUrl = $baseUrl'); return this; } catch(e) { print('Error in ApiService initialization: $e'); rethrow; // エラーを再スローして、呼び出し元で処理できるようにする //return this; } } /* このメソッドは以下のように動作します: まず、渡された type パラメータに基づいて、どのクラスのフィールドを扱っているかを判断します。 次に、クラス内で fieldName に対応する期待される型を返します。 クラスや フィールド名が予期されていないものである場合、'Unknown' または 'Unknown Type' を返します。 このメソッドを ApiService クラスに追加することで、_printDataComparison メソッドは各フィールドの期待される型を正確に表示できるようになります。 さらに、このメソッドを使用することで、API レスポンスのデータ型が期待と異なる場合に簡単に検出できるようになります。例えば、Category クラスの duration フィールドが整数型(秒数)で期待されているのに対し、API が文字列を返した場合、すぐに問題を特定できます。 注意点として、API のレスポンス形式が変更された場合や、新しいフィールドが追加された場合は、このメソッドも更新する必要があります。そのため、API の変更とクライアントサイドのコードの同期を保つことが重要です。 */ Future getToken2 () async { // IndexControllerの初期化を待つ if (!Get.isRegistered()) { Get.find(); } final indexController = Get.find(); if (indexController.currentUser.isNotEmpty) { token = indexController.currentUser[0]['token'] ?? ''; //print("Get token = $token"); }else{ token = ""; } return token; } String getToken() { // IndexControllerの初期化を待つ if (!Get.isRegistered()) { Get.find(); } final indexController = Get.find(); if (indexController.currentUser.isNotEmpty) { token = indexController.currentUser[0]['token'] ?? ''; //print("Get token = $token"); }else{ token = ""; } return token; } Future _handleRequest(Future Function() request) async { try { final response = await request(); if (response.statusCode == 200 || response.statusCode == 201 ) { return json.decode(utf8.decode(response.bodyBytes)); } else if (response.statusCode == 401) { await _handleUnauthorized(); throw Exception('Authentication failed. Please log in again.'); } else { throw Exception('Request failed with status: ${response.statusCode}'); } } catch (e) { print('API request error: $e'); rethrow; } } Future _handleUnauthorized() async { // トークンをクリアし、ユーザーをログアウトさせる final indexController = Get.find(); await indexController.logout(); Get.offAllNamed(AppPages.LOGIN); } Future> getTeams() async { final token = await getToken2(); return _handleRequest(() => http.get( Uri.parse('$baseUrl/teams/'), headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"}, )).then((data) => (data as List).map((json) => Team.fromJson(json)).toList()); } Future> getCategories() async { init(); getToken(); try { final response = await http.get( Uri.parse('$baseUrl/categories/'), headers: {'Authorization': 'Token $token',"Content-Type": "application/json; charset=UTF-8"}, ); if (response.statusCode == 200) { final decodedResponse = utf8.decode(response.bodyBytes); //print('User Response body: $decodedResponse'); List categoriesJson = json.decode(decodedResponse); List categories = []; for (var categoryJson in categoriesJson) { try { //print('\nCategory Data:'); //_printDataComparison(categoryJson, NewCategory); categories.add(NewCategory.fromJson(categoryJson)); }catch(e){ print('Error parsing category: $e'); print('Problematic JSON: $categoryJson'); } } return categories; } else { throw Exception( 'Failed to load categories. Status code: ${response.statusCode}'); } }catch(e, stackTrace){ print('Error in getCategories: $e'); print('Stack trace: $stackTrace'); rethrow; } } Future getZekkenNumber(int categoryId) async { final token = await getToken2(); return _handleRequest(() => http.post( Uri.parse('$baseUrl/categories-viewset/$categoryId/get_zekken_number/'), headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"}, )).then((data) => NewCategory.fromJson(data)); } Future getCurrentUser() async { init(); final token = getToken(); try { final response = await http.get( Uri.parse('$baseUrl/user/'), headers: {'Authorization': 'Token $token',"Content-Type": "application/json; charset=UTF-8"}, ); if (response.statusCode == 200) { final decodedResponse = utf8.decode(response.bodyBytes); //print('User Response body: $decodedResponse'); final jsonData = json.decode(decodedResponse); //print('\nUser Data Comparison:'); //_printDataComparison(jsonData, User); return User.fromJson(jsonData); } else if (response.statusCode == 401) { // トークンが無効な場合、ログアウトしてログインページにリダイレクト await Get.find().logout(); //indexController.logout(); Get.offAllNamed(AppPages.LOGIN); throw Exception('Authentication failed. Please log in again.'); } else { throw Exception('Failed to get current user. Status code: ${response.statusCode}'); } } catch (e, stackTrace) { print('Error in getCurrentUser: $e'); print('Stack trace: $stackTrace'); rethrow; } } void _printDataComparison(Map data, Type expectedType) { print('Field\t\t| Expected Type\t| Actual Type\t| Actual Value'); print('------------------------------------------------------------'); data.forEach((key, value) { String expectedFieldType = _getExpectedFieldType(expectedType, key); _printComparison(key, expectedFieldType, value); }); } String _getExpectedFieldType(Type type, String fieldName) { // This method should return the expected type for each field based on the class definition // You might need to implement this based on your class structures switch (type) { case NewCategory: switch (fieldName) { case 'id': return 'int'; case 'category_name': return 'String'; case 'category_number': return 'int'; case 'duration': return 'int (seconds)'; case 'num_of_member': return 'int'; case 'family': return 'bool'; case 'female': return 'bool'; default: return 'Unknown'; } case Team: switch (fieldName) { case 'id': return 'int'; case 'zekken_number': return 'String'; case 'team_name': return 'String'; case 'category': return 'NewCategory (Object)'; case 'owner': return 'User (Object)'; default: return 'Unknown'; } case User: switch (fieldName) { case 'id': return 'int'; case 'email': return 'String'; case 'firstname': return 'String'; case 'lastname': return 'String'; case 'date_of_birth': return 'String (ISO8601)'; case 'female': return 'bool'; case 'is_active': return 'bool'; default: return 'Unknown'; } default: return 'Unknown Type'; } } void _printComparison(String fieldName, String expectedType, dynamic actualValue) { String actualType = actualValue?.runtimeType.toString() ?? 'null'; String displayValue = actualValue.toString(); if (displayValue.length > 50) { displayValue = '${displayValue.substring(0, 47)}...'; } print('$fieldName\t\t| $expectedType\t\t| $actualType\t\t| $displayValue'); } Future createTeam(String teamName, int categoryId) async { final token = await getToken2(); return _handleRequest(() => http.post( Uri.parse('$baseUrl/teams/'), headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"}, body: json.encode({'team_name': teamName, 'category': categoryId}), )).then((data) => Team.fromJson(data)); } Future updateTeam(int teamId, String teamName, int categoryId) async { final token = await getToken2(); return _handleRequest(() => http.put( Uri.parse('$baseUrl/teams/$teamId/'), headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"}, body: json.encode({'team_name': teamName, 'category': categoryId}), )).then((data) => Team.fromJson(data)); } Future deleteTeam(int teamId) async { final token = await getToken2(); await _handleRequest(() => http.delete( Uri.parse('$baseUrl/teams/$teamId/'), headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"}, )); } Future> getTeamMembers(int teamId) async { final token = await getToken2(); return _handleRequest(() => http.get( Uri.parse('$baseUrl/teams/$teamId/members/'), headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"}, )).then((data) => (data as List).map((json) => User.fromJson(json)).toList()); } Future createTeamMember(int teamId, String? email, String? firstname, String? lastname, DateTime? dateOfBirth, bool? female) async { final token = await getToken2(); String? formattedDateOfBirth = dateOfBirth != null ? DateFormat('yyyy-MM-dd').format(dateOfBirth) : null; return _handleRequest(() => http.post( Uri.parse('$baseUrl/teams/$teamId/members/'), headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"}, body: json.encode({ 'email': email, 'firstname': firstname, 'lastname': lastname, 'date_of_birth': formattedDateOfBirth, 'female': female, }), )).then((data) => User.fromJson(data)); } Future updateTeamMember(int teamId, int? memberId, String firstname, String lastname, DateTime? dateOfBirth, bool? female) async { final token = await getToken2(); String? formattedDateOfBirth = dateOfBirth != null ? DateFormat('yyyy-MM-dd').format(dateOfBirth) : null; return _handleRequest(() => http.put( Uri.parse('$baseUrl/teams/$teamId/members/$memberId/'), headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"}, body: json.encode({ 'firstname': firstname, 'lastname': lastname, 'date_of_birth': formattedDateOfBirth, 'female': female, }), )).then((data) => User.fromJson(data)); } Future deleteTeamMember(int teamId, int memberId) async { final token = await getToken2(); await _handleRequest(() => http.delete( Uri.parse('$baseUrl/teams/$teamId/members/$memberId/'), headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"}, )); } Future deleteAllTeamMembers(int teamId) async { final response = await http.delete( Uri.parse('$baseUrl/teams/$teamId/members/destroy_all/?confirm=true'), headers: {'Authorization': 'Token $token'}, ); if (response.statusCode != 200) { throw Exception('Failed to delete team members'); } } Future resendMemberInvitation(int memberId) async { init(); getToken(); final response = await http.post( Uri.parse('$baseUrl/members/$memberId/resend-invitation/'), headers: {'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode != 200) { throw Exception('Failed to resend invitation'); } } Future> getEntries() async { final token = await getToken2(); return _handleRequest(() => http.get( Uri.parse('$baseUrl/entry/'), headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"}, )).then((data) => (data as List).map((json) => Entry.fromJson(json)).toList()); } Future> getTeamEntries(int teamId) async { try { final response = await http.get( Uri.parse('$baseUrl/teams/$teamId/entries'), headers: {'Authorization': 'Token $token'}, ); if (response.statusCode == 200) { List entriesJson = jsonDecode(response.body); return entriesJson.map((json) => Entry.fromJson(json)).toList(); } else { throw Exception('Failed to load team entries: ${response.statusCode}'); } } catch (e) { print('Error in getTeamEntries: $e'); rethrow; } } Future updateEntryStatus(int entryId, bool hasParticipated, bool hasGoaled) async { final token = await getToken2(); return _handleRequest(() => http.patch( Uri.parse('$baseUrl/entries/$entryId/update-status/'), headers: { 'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8' }, body: json.encode({ 'hasParticipated': hasParticipated, 'hasGoaled': hasGoaled, }), )).then((data) => Entry.fromJson(data)); } Future> getEvents() async { init(); getToken(); final response = await http.get( Uri.parse('$baseUrl/new-events/',), headers: {'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode == 200) { final decodedResponse = utf8.decode(response.bodyBytes); print('Response body: $decodedResponse'); List eventsJson = json.decode(decodedResponse); return eventsJson.map((json) => Event.fromJson(json)).toList(); } else { throw Exception('Failed to load events'); } } Future createEntry(int teamId, int eventId, int categoryId, DateTime date, String zekkenNumber) async { final token = await getToken2(); String formattedDate = DateFormat('yyyy-MM-dd').format(date); return _handleRequest(() => http.post( Uri.parse('$baseUrl/entry/'), headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"}, body: json.encode({ 'team': teamId, 'event': eventId, 'category': categoryId, 'date': formattedDate, 'zekken_number': zekkenNumber, }), )).then((data) => Entry.fromJson(data)); } Future updateUserInfo(int userId, Entry entry) async { final token = await getToken2(); String formattedDate = DateFormat('yyyy-MM-dd').format(entry.date!); await _handleRequest(() => http.put( Uri.parse('$baseUrl/userinfo/$userId/'), headers: {'Authorization': 'Token $token', "Content-Type": "application/json; charset=UTF-8"}, body: json.encode({ 'zekken_number': entry.zekkenNumber, 'event_code': entry.event.eventName, 'group': entry.team.category.categoryName, 'team_name': entry.team.teamName, 'date': formattedDate, }), )); } Future updateEntry(int entryId, int teamId, int eventId, int categoryId, DateTime date,int zekkenNumber) async { init(); getToken(); String? formattedDate; formattedDate = DateFormat('yyyy-MM-dd').format(date); final response = await http.put( Uri.parse('$baseUrl/entry/$entryId/'), headers: { 'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8', }, body: json.encode({ 'team': teamId, 'event': eventId, 'category': categoryId, 'date': formattedDate, 'zekken_number': zekkenNumber, }), ); if (response.statusCode == 200) { final decodedResponse = utf8.decode(response.bodyBytes); return Entry.fromJson(json.decode(decodedResponse)); } else { final decodedResponse = utf8.decode(response.bodyBytes); final blk = json.decode(decodedResponse); Map error_dict = blk[0]['error']; String ? error_message = error_dict['non_field_errors'][0].string; throw Exception(error_message); } } Future deleteEntry(int entryId) async { init(); getToken(); final response = await http.delete( Uri.parse('$baseUrl/entry/$entryId/'), headers: {'Authorization': 'Token $token'}, ); if (response.statusCode != 204) { throw Exception('Failed to delete entry'); } } static Future updateUserDetail(User user, String token) async { String serverUrl = ConstValues.currentServer(); int? userid = user.id; String url = '$serverUrl/api/userdetail/$userid/'; try { String? formattedDate; if (user.dateOfBirth != null) { formattedDate = DateFormat('yyyy-MM-dd').format(user.dateOfBirth!); } final http.Response response = await http.put( Uri.parse(url), headers: { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': 'Token $token' }, body: jsonEncode({ 'firstname': user.firstname, 'lastname': user.lastname, 'date_of_birth': formattedDate, 'female': user.female, }), ); if (response.statusCode == 200) { return true; } else { print('Update failed with status code: ${response.statusCode}'); return false; } } catch (e) { print('Error in updateUserDetail: $e'); return false; } } Future resetPassword(String email) async { init(); try { final response = await http.post( Uri.parse('$baseUrl/password-reset/'), headers: { 'Content-Type': 'application/json; charset=UTF-8', }, body: json.encode({ 'email': email, }), ); if (response.statusCode == 200) { return true; } else { print('Password reset failed with status code: ${response.statusCode}'); return false; } } catch (e) { print('Error in resetPassword: $e'); return false; } } Future getLastGoalTime(int userId) async { try { final response = await http.get( Uri.parse('$baseUrl/users/$userId/last-goal/'), headers: { 'Authorization': 'Token $token', 'Content-Type': 'application/json; charset=UTF-8', }, ); if (response.statusCode == 200) { final decodedResponse = json.decode(utf8.decode(response.bodyBytes)); if (decodedResponse['last_goal_time'] != null) { return DateTime.parse(decodedResponse['last_goal_time']).toLocal(); } } else { print('Failed to get last goal time. Status code: ${response.statusCode}'); } } catch (e) { print('Error in getLastGoalTime: $e'); } return null; } }