web: migrate health notifications to js_interop; add browser hook
Some checks failed
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled
Flutter Test & Quality Check / Build APK (push) Has been cancelled

- Replace dart:js with package:js in health_check_service_web.dart\n- Implement showHealthCheckNotification in web/index.html\n- Pin js dependency to ^0.6.7 for flutter_secure_storage_web compatibility

auth: harden AuthInterceptor + tests

- Allow overrideAuthRepository injection for testing\n- Normalize imports to package: paths\n- Add unit test covering token attach, 401→refresh→retry, and failure path\n- Add integration test skeleton gated by env vars

ui/data: map User.companyName to list column

- Add companyName to domain User\n- Map UserDto.company?.name\n- Render companyName in user_list

cleanup: remove legacy equipment table + unused code; minor warnings

- Remove _buildFlexibleTable and unused helpers\n- Remove unused zipcode details and cache retry constant\n- Fix null-aware and non-null assertions\n- Address child-last warnings in administrator dialog

docs: update AGENTS.md session context
This commit is contained in:
JiWoong Sul
2025-09-08 17:39:00 +09:00
parent 519e1883a3
commit 655d473413
55 changed files with 2729 additions and 4968 deletions

View File

@@ -1,24 +1,28 @@
import 'package:dio/dio.dart';
import 'package:get_it/get_it.dart';
import 'package:flutter/foundation.dart';
import '../../../../core/constants/api_endpoints.dart';
import '../../../../services/auth_service.dart';
import '../../../../core/config/environment.dart';
import 'package:superport/core/constants/api_endpoints.dart';
import 'package:superport/domain/repositories/auth_repository.dart';
import 'package:superport/data/models/auth/refresh_token_request.dart';
import 'package:superport/core/config/environment.dart';
import 'package:superport/core/navigation/app_navigator.dart';
/// 인증 인터셉터
class AuthInterceptor extends Interceptor {
AuthService? _authService;
AuthRepository? _authRepository;
final Dio dio;
AuthInterceptor(this.dio);
AuthInterceptor(this.dio, {AuthRepository? overrideAuthRepository}) {
_authRepository = overrideAuthRepository;
}
AuthService? get authService {
AuthRepository? get authRepository {
try {
_authService ??= GetIt.instance<AuthService>();
return _authService;
_authRepository ??= GetIt.instance<AuthRepository>();
return _authRepository;
} catch (e) {
if (kDebugMode) {
debugPrint('Failed to get AuthService in AuthInterceptor: $e');
debugPrint('Failed to get AuthRepository in AuthInterceptor: $e');
}
return null;
}
@@ -43,21 +47,22 @@ class AuthInterceptor extends Interceptor {
}
// 저장된 액세스 토큰 가져오기
final service = authService;
final repo = authRepository;
if (Environment.enableLogging && kDebugMode) {
debugPrint('[AuthInterceptor] AuthService available: ${service != null}');
debugPrint('[AuthInterceptor] AuthRepository available: ${repo != null}');
}
if (service != null) {
final accessToken = await service.getAccessToken();
if (repo != null) {
final tokenEither = await repo.getStoredAccessToken();
final accessToken = tokenEither.fold((_) => null, (t) => t);
if (Environment.enableLogging && kDebugMode) {
debugPrint('[AuthInterceptor] Access token retrieved: ${accessToken != null ? 'Yes (${accessToken.substring(0, 10)}...)' : 'No'}');
debugPrint('[AuthInterceptor] Access token retrieved: ${accessToken != null ? 'Yes' : 'No'}');
}
if (accessToken != null) {
options.headers['Authorization'] = 'Bearer $accessToken';
if (Environment.enableLogging && kDebugMode) {
debugPrint('[AuthInterceptor] Authorization header set: Bearer ${accessToken.substring(0, 10)}...');
debugPrint('[AuthInterceptor] Authorization header set');
}
} else {
if (Environment.enableLogging && kDebugMode) {
@@ -66,7 +71,7 @@ class AuthInterceptor extends Interceptor {
}
} else {
if (Environment.enableLogging && kDebugMode) {
debugPrint('[AuthInterceptor] ERROR: AuthService not available from GetIt');
debugPrint('[AuthInterceptor] ERROR: AuthRepository not available from GetIt');
}
}
@@ -96,33 +101,25 @@ class AuthInterceptor extends Interceptor {
return;
}
final service = authService;
if (service != null) {
final repo = authRepository;
if (repo != null) {
if (Environment.enableLogging && kDebugMode) {
debugPrint('[AuthInterceptor] Attempting token refresh...');
}
// 토큰 갱신 시도
final refreshResult = await service.refreshToken();
final refreshTokenEither = await repo.getStoredRefreshToken();
final refreshToken = refreshTokenEither.fold((_) => null, (t) => t);
final refreshResult = refreshToken == null
? null
: await repo.refreshToken(RefreshTokenRequest(refreshToken: refreshToken));
final refreshSuccess = refreshResult.fold(
(failure) {
if (Environment.enableLogging && kDebugMode) {
debugPrint('[AuthInterceptor] Token refresh failed: ${failure.message}');
}
return false;
},
(tokenResponse) {
if (Environment.enableLogging && kDebugMode) {
debugPrint('[AuthInterceptor] Token refresh successful');
}
return true;
},
);
final refreshSuccess = refreshResult != null && refreshResult.isRight();
if (refreshSuccess) {
// 새로운 토큰으로 원래 요청 재시도
try {
final newAccessToken = await service.getAccessToken();
final newAccessTokenEither = await repo.getStoredAccessToken();
final newAccessToken = newAccessTokenEither.fold((_) => null, (t) => t);
if (newAccessToken != null) {
if (Environment.enableLogging && kDebugMode) {
@@ -149,8 +146,9 @@ class AuthInterceptor extends Interceptor {
if (Environment.enableLogging && kDebugMode) {
debugPrint('[AuthInterceptor] Clearing session due to auth failure');
}
await service.clearSession();
// TODO: Navigate to login screen
await repo.clearLocalSession();
// 로그인 화면으로 이동 (모든 스택 제거)
navigateToLoginClearingStack();
}
}
@@ -164,4 +162,4 @@ class AuthInterceptor extends Interceptor {
path.contains(ApiEndpoints.refresh) ||
path.contains(ApiEndpoints.logout);
}
}
}