feat(client): invite api + provider + auth_api.register 加 inviteCode
This commit is contained in:
@@ -115,12 +115,14 @@ class AuthApi {
|
||||
required String code,
|
||||
required String password,
|
||||
Map<String, dynamic>? device,
|
||||
String? inviteCode,
|
||||
}) async {
|
||||
final resp = await _post('/v1/auth/register', {
|
||||
'email': email,
|
||||
'code': code,
|
||||
'password': password,
|
||||
if (device != null) 'device': device,
|
||||
if (inviteCode != null && inviteCode.isNotEmpty) 'invite_code': inviteCode,
|
||||
});
|
||||
if (resp.statusCode != 200 && resp.statusCode != 201) {
|
||||
_throwFromResponse(resp);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// invite_api.dart — 邀请/奖励任务代理端点封装(JWT 经 ApiClient 自动注入)。
|
||||
import 'api_client.dart';
|
||||
|
||||
class InviteInfo {
|
||||
const InviteInfo({
|
||||
required this.code, required this.link, required this.invited,
|
||||
required this.converted, required this.earnedDays,
|
||||
required this.tgEnabled, required this.tgJoined, required this.channel,
|
||||
});
|
||||
final String code, link, channel;
|
||||
final int invited, converted, earnedDays;
|
||||
final bool tgEnabled, tgJoined;
|
||||
|
||||
factory InviteInfo.fromJson(Map<String, dynamic> j) {
|
||||
final tg = (j['telegram'] as Map<String, dynamic>?) ?? const {};
|
||||
return InviteInfo(
|
||||
code: j['invite_code'] as String? ?? '',
|
||||
link: j['invite_link'] as String? ?? '',
|
||||
invited: (j['invited'] as num?)?.toInt() ?? 0,
|
||||
converted: (j['converted'] as num?)?.toInt() ?? 0,
|
||||
earnedDays: (j['earned_days'] as num?)?.toInt() ?? 0,
|
||||
tgEnabled: tg['enabled'] as bool? ?? false,
|
||||
tgJoined: tg['joined'] as bool? ?? false,
|
||||
channel: tg['channel'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class InviteApi {
|
||||
InviteApi(this._c);
|
||||
final ApiClient _c;
|
||||
Future<InviteInfo> fetch() async => InviteInfo.fromJson(await _c.getJson('/v1/invite'));
|
||||
Future<String> telegramStartLink() async =>
|
||||
(await _c.getJson('/v1/tasks/telegram/start'))['deep_link'] as String? ?? '';
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// invite_provider.dart — 邀请信息状态装配:复用 apiClientProvider,未登录不打网络。
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../services/invite_api.dart';
|
||||
import 'account_providers.dart';
|
||||
import 'auth_provider.dart';
|
||||
|
||||
final inviteApiProvider = Provider<InviteApi>((ref) => InviteApi(ref.watch(apiClientProvider)));
|
||||
|
||||
class InviteNotifier extends AsyncNotifier<InviteInfo?> {
|
||||
@override
|
||||
Future<InviteInfo?> build() async {
|
||||
// 未登录返回 null(不打网络);已登录拉取。
|
||||
final token = ref.watch(authProvider).accessToken;
|
||||
if (token == null || token.isEmpty) return null;
|
||||
return ref.read(inviteApiProvider).fetch();
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
state = const AsyncLoading();
|
||||
state = await AsyncValue.guard(() => ref.read(inviteApiProvider).fetch());
|
||||
}
|
||||
}
|
||||
|
||||
final inviteProvider = AsyncNotifierProvider<InviteNotifier, InviteInfo?>(InviteNotifier.new);
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/testing.dart';
|
||||
import 'package:pangolin_vpn/services/api_client.dart';
|
||||
import 'package:pangolin_vpn/services/invite_api.dart';
|
||||
|
||||
ApiClient _c(MockClient m) => ApiClient(baseUrl: 'http://x', getToken: () => 't', refresh: () async => false, client: m);
|
||||
|
||||
void main() {
|
||||
test('fetch 解析邀请信息 + TG 任务态', () async {
|
||||
final api = InviteApi(_c(MockClient((req) async {
|
||||
expect(req.url.path, '/v1/invite');
|
||||
return http.Response('{"invite_code":"ABCD2345","invite_link":"https://x/i/ABCD2345",'
|
||||
'"invited":3,"converted":1,"earned_days":16,'
|
||||
'"telegram":{"enabled":true,"joined":false,"channel":"@pangolin_app"}}', 200);
|
||||
})));
|
||||
final info = await api.fetch();
|
||||
expect(info.code, 'ABCD2345');
|
||||
expect(info.invited, 3);
|
||||
expect(info.earnedDays, 16);
|
||||
expect(info.tgEnabled, true);
|
||||
expect(info.tgJoined, false);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user