import 'package:uuid/uuid.dart'; import '../../contracts/contracts.dart'; import '../errors.dart'; import '../ports/emby_gateway.dart'; import '../ports/server_repository.dart'; import '../ports/session_repository.dart'; const _uuid = Uuid(); class ListServersUseCase { final ServerRepository serverRepository; ListServersUseCase({required this.serverRepository}); Future> execute() => serverRepository.list(); } class ReorderServersUseCase { final ServerRepository serverRepository; ReorderServersUseCase({required this.serverRepository}); Future execute(List orderedServers) { return serverRepository.replaceAll(orderedServers); } } class ProbeServerUseCase { final EmbyGateway embyGateway; ProbeServerUseCase({required this.embyGateway}); Future execute(EmbyServerProbeReq input) async { final baseUrl = input.baseUrl.trim(); if (baseUrl.isEmpty) { throw DomainError(ErrorCode.invalidParams, '服务器地址不能为空'); } return embyGateway.probePublicInfo(baseUrl); } } class SaveServerUseCase { final ServerRepository serverRepository; final DateTime Function() now; final String Function() idGenerator; SaveServerUseCase({ required this.serverRepository, DateTime Function()? now, String Function()? idGenerator, }) : now = (now ?? DateTime.now), idGenerator = (idGenerator ?? _defaultIdGenerator); static String _defaultIdGenerator() => _uuid.v4(); Future execute(EmbyServerSaveReq input) async { final name = input.name.trim(); if (name.isEmpty) { throw DomainError(ErrorCode.invalidParams, '服务器名称不能为空'); } final baseUrl = _normalizeBaseUrl(input.baseUrl); final nowIso = now().toUtc().toIso8601String(); final duplicated = await serverRepository.findByBaseUrl(baseUrl); if (duplicated != null && duplicated.id != input.id) { throw DomainError( ErrorCode.storeConflict, '服务器地址已存在', details: {'baseUrl': baseUrl}, ); } final iconUrl = input.iconUrl.trim(); if (input.id != null) { final current = await serverRepository.findById(input.id!); if (current == null) { throw DomainError( ErrorCode.storeNotFound, '服务器不存在', details: {'id': input.id}, ); } return serverRepository.update( current.copyWith( name: name, baseUrl: baseUrl, updatedAt: nowIso, iconUrl: iconUrl, ), ); } return serverRepository.create( EmbyServer( id: idGenerator(), name: name, baseUrl: baseUrl, createdAt: nowIso, updatedAt: nowIso, iconUrl: iconUrl, ), ); } String _normalizeBaseUrl(String raw) { final trimmed = raw.trim(); if (trimmed.isEmpty) { throw DomainError(ErrorCode.invalidParams, '服务器地址不能为空'); } Uri parsed; try { parsed = Uri.parse(trimmed); } catch (_) { throw DomainError( ErrorCode.invalidParams, '服务器地址格式不正确', details: {'baseUrl': raw}, ); } if (parsed.scheme != 'http' && parsed.scheme != 'https') { throw DomainError( ErrorCode.invalidParams, '仅支持 HTTP/HTTPS 协议', details: {'baseUrl': raw}, ); } final origin = '${parsed.scheme}://${parsed.host}' '${parsed.hasPort ? ':${parsed.port}' : ''}'; final cleanPath = parsed.path.replaceAll(RegExp(r'/+$'), ''); final normalizedPath = cleanPath.isEmpty ? '/' : cleanPath; if (RegExp(r'/emby$', caseSensitive: false).hasMatch(normalizedPath)) { return '$origin$normalizedPath'; } if (normalizedPath == '/') { return '$origin/emby'; } return '$origin$normalizedPath/emby'; } } class TogglePauseServerUseCase { final ServerRepository serverRepository; TogglePauseServerUseCase({required this.serverRepository}); Future execute(String id) async { final current = await serverRepository.findById(id); if (current == null) { throw DomainError(ErrorCode.storeNotFound, '服务器不存在', details: {'id': id}); } return serverRepository.update(current.copyWith(paused: !current.paused)); } } class DeleteServerUseCase { final ServerRepository serverRepository; final SessionRepository sessionRepository; DeleteServerUseCase({ required this.serverRepository, required this.sessionRepository, }); Future execute(String id) async { final exists = await serverRepository.findById(id); if (exists == null) { throw DomainError(ErrorCode.storeNotFound, '服务器不存在', details: {'id': id}); } await serverRepository.deleteById(id); await sessionRepository.clear(id); } }