import '../../core/data/backend.dart'; import 'station_data.dart'; /// STATION 허브의 실 서버 구현 — S-S-HUB. /// /// `stationLinks` 는 운영만 쓰고 로그인 사용자는 읽기만 한다(보안 규칙 §7). /// 정렬은 팬덤별 `rank` 오름차순이며, 이 순서가 곧 운영이 정한 노출 순서다. /// 앱에서 다시 정렬하지 않는다 — 그러면 운영 화면과 앱이 다르게 보인다. class FirestoreStationRepository implements StationRepository { FirestoreStationRepository(this._backend); final Backend _backend; /// 소속 팬덤. 라우터가 모르는 값이라 사용자 문서에서 한 번 읽어 들고 있는다. String? _fandomId; @override Future load() async { final cached = _fandomId; final String id; if (cached != null) { id = cached; } else { final snap = await _backend.db.doc('users/${_backend.requireUid}').get(); id = _fandomId = (snap.data() ?? const {}).strOr('fandomId'); } // 팬덤이 없으면 큐레이션 대상이 정해지지 않는다. 빈 상태로 두고 화면이 안내한다. if (id.isEmpty) { return const StationSnapshot(links: [], canSuggest: false); } final snap = await _backend.db .collection('stationLinks') .where('fandomId', isEqualTo: id) .orderBy('rank') .limit(30) .get(); return StationSnapshot( links: [ for (final doc in snap.docs) StationLink( id: doc.id, title: doc.data().strOr('title'), url: doc.data().strOr('url'), category: _category(doc.data().strOr('category')), description: doc.data().strOr('description'), verified: doc.data()['verified'] == true, ), ], canSuggest: _backend.uid != null, ); } /// 모르는 값은 「공식」으로 떨어뜨리지 않고 판매처로 본다 — 공식 배지를 /// 검증하지 않은 링크에 붙이는 쪽이 더 위험하다. StationCategory _category(String raw) => switch (raw) { 'official' => StationCategory.official, 'ticket' => StationCategory.ticket, 'media' => StationCategory.media, _ => StationCategory.shop, }; }