import 'dart:async'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import '../config/api_config.dart'; import '../utils/web_storage.dart'; abstract class PublicHttpClient { Future> get(String path, {Map? headers}); Future> post(String path, {dynamic data, Map? headers}); Future> put(String path, {dynamic data, Map? headers}); } class DioPublicHttpClient implements PublicHttpClient { final Dio _dio; DioPublicHttpClient({Dio? dio}) : _dio = dio ?? Dio(BaseOptions( baseUrl: ApiConfig.baseUrl, connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 10), contentType: 'application/json', )); @override Future get(String path, {Map? headers}) { return _dio.get(path, options: Options(headers: headers)); } @override Future post(String path, {data, Map? headers}) { return _dio.post(path, data: data ?? {}, options: Options(headers: headers)); } @override Future put(String path, {data, Map? headers}) { return _dio.put(path, data: data ?? {}, options: Options(headers: headers)); } } class PublicEventService { final PublicHttpClient _http; PublicEventService() : _http = DioPublicHttpClient(); @visibleForTesting PublicEventService.forTest(this._http); static String tokenKey(String publicHash) => 'event_token_' + publicHash; String? getSavedToken(String publicHash) { return WebStorage.getItem(tokenKey(publicHash)); } void saveToken(String publicHash, String token) { WebStorage.setItem(tokenKey(publicHash), token); } void removeToken(String publicHash) { WebStorage.removeItem(tokenKey(publicHash)); } Map? _authHeader(String? token) { if (token == null || token.isEmpty) return null; return { 'Authorization': 'Bearer ' + token }; } Future> fetchEvent(String publicHash) async { final saved = getSavedToken(publicHash); // 백엔드는 GET이 아니라 POST('/:publicHash')로 최초 진입/토큰 인증을 처리함 final res = await _http.post( '/public/' + publicHash, data: {}, headers: _authHeader(saved), ); final data = res.data as Map; // 응답에 token이 있으면 저장 (토큰 재발급 케이스) final token = data['token']?.toString(); if (token != null && token.isNotEmpty) { saveToken(publicHash, token); } return data; } Future> enter(String publicHash, {String? password}) async { final saved = getSavedToken(publicHash); try { // 우선 저장된 토큰으로 시도, 실패 시 비밀번호로 재시도는 서버가 처리 final res = await _http.post( '/public/' + publicHash, data: password != null ? { 'password': password } : {}, headers: _authHeader(saved), ); final data = res.data as Map; // 응답에 token이 있으면 저장 final token = data['token']?.toString(); if (token != null && token.isNotEmpty) { saveToken(publicHash, token); } return data; } on DioException catch (e) { final status = e.response?.statusCode; if (status == 401 || status == 403) { removeToken(publicHash); } rethrow; } } Future addOrUpdateParticipant(String publicHash, { String? participantId, required String memberId, required String status, required String paymentStatus, String? comment, }) async { final token = getSavedToken(publicHash); final payload = { if (participantId != null) 'participantId': participantId, 'memberId': memberId, 'status': status, 'paymentStatus': paymentStatus, if (comment != null) 'comment': comment, }; await _http.post( '/public/' + publicHash + '/participant', data: payload, headers: _authHeader(token), ); } Future addGuest(String publicHash, { required String name, String? phone, String? gender, num? average, }) async { final token = getSavedToken(publicHash); final payload = { 'name': name, if (phone != null) 'phone': phone, if (gender != null) 'gender': gender, if (average != null) 'average': average, }; await _http.post( '/public/' + publicHash + '/guest', data: payload, headers: _authHeader(token), ); } Future updateScore(String publicHash, { required String participantId, required int gameNumber, required int score, int? handicap, dynamic teamNumber, }) async { final token = getSavedToken(publicHash); final payload = { 'participantId': participantId, 'gameNumber': gameNumber, 'score': score, if (handicap != null) 'handicap': handicap, if (teamNumber != null) 'teamNumber': teamNumber, }; await _http.put( '/public/' + publicHash + '/score', data: payload, headers: _authHeader(token), ); } Future updateTeamHandicap(String publicHash, { required int teamNumber, required int handicap, }) async { final token = getSavedToken(publicHash); await _http.put( '/public/' + publicHash + '/team/' + teamNumber.toString() + '/handicap', data: { 'teamNumber': teamNumber, 'handicap': handicap }, headers: _authHeader(token), ); } }