Files
fanuzu/app/lib/features/station/station_repository_firestore.dart
T

64 lines
2.2 KiB
Dart

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<StationSnapshot> 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,
};
}