feat(profile): integrate referral and authorization APIs for profile page

- Add Kong routes for identity-service /me, referral-service, and authorization-service
- Create AuthorizationService in Flutter for fetching user authorizations
- Extend ReferralService with getMyReferralInfo() and getDirectReferrals() methods
- Update profile_page.dart to display real team stats from APIs
- Fix authorization-service JWT strategy to accept identity-service token format
- Add decimal.js dependency to authorization-service
- Add prisma migration file for authorization-service

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-09 01:57:39 -08:00
parent 15f617c9a9
commit d0487c4a7e
11 changed files with 924 additions and 28 deletions

View File

@ -32,6 +32,10 @@ services:
paths:
- /api/v1/auth
strip_path: false
- name: identity-me
paths:
- /api/v1/me
strip_path: false
- name: identity-user
paths:
- /api/v1/user
@ -90,9 +94,21 @@ services:
url: http://192.168.1.111:3004
routes:
- name: referral-api
paths:
- /api/v1/referral
strip_path: false
- name: referral-referrals
paths:
- /api/v1/referrals
strip_path: false
- name: referral-leaderboard
paths:
- /api/v1/leaderboard
strip_path: false
- name: referral-team-statistics
paths:
- /api/v1/team-statistics
strip_path: false
# ---------------------------------------------------------------------------
# Reward Service - 奖励服务
@ -151,9 +167,11 @@ services:
routes:
- name: authorization-api
paths:
- /api/v1/authorization
- /api/v1/permissions
- /api/v1/roles
- /api/v1/authorizations
strip_path: false
- name: authorization-admin
paths:
- /api/v1/admin/authorizations
strip_path: false
# ---------------------------------------------------------------------------
@ -182,6 +200,21 @@ services:
- /api/v1/presence
strip_path: false
# ---------------------------------------------------------------------------
# Blockchain Service - 区块链服务
# ---------------------------------------------------------------------------
- name: blockchain-service
url: http://192.168.1.111:3012
routes:
- name: blockchain-deposit
paths:
- /api/v1/deposit
strip_path: false
- name: blockchain-balance
paths:
- /api/v1/balance
strip_path: false
# =============================================================================
# Plugins - 全局插件配置
# =============================================================================

View File

@ -43,5 +43,4 @@ lerna-debug.log*
!.env.example
# Prisma
prisma/migrations/*
!prisma/migrations/.gitkeep
prisma/migrations/**/migration_lock.toml

View File

@ -21,6 +21,7 @@
"@prisma/client": "^5.7.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"decimal.js": "^10.4.3",
"ioredis": "^5.3.2",
"kafkajs": "^2.2.4",
"passport-jwt": "^4.0.1",
@ -246,6 +247,7 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@ -4272,6 +4274,12 @@
}
}
},
"node_modules/decimal.js": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
"license": "MIT"
},
"node_modules/dedent": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz",

View File

@ -43,6 +43,7 @@
"@prisma/client": "^5.7.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"decimal.js": "^10.4.3",
"ioredis": "^5.3.2",
"kafkajs": "^2.2.4",
"passport-jwt": "^4.0.1",

View File

@ -0,0 +1,371 @@
-- CreateEnum
CREATE TYPE "RoleType" AS ENUM ('COMMUNITY', 'AUTH_PROVINCE_COMPANY', 'PROVINCE_COMPANY', 'AUTH_CITY_COMPANY', 'CITY_COMPANY');
-- CreateEnum
CREATE TYPE "AuthorizationStatus" AS ENUM ('PENDING', 'AUTHORIZED', 'REVOKED');
-- CreateEnum
CREATE TYPE "AssessmentResult" AS ENUM ('NOT_ASSESSED', 'PASS', 'FAIL', 'BYPASSED');
-- CreateEnum
CREATE TYPE "MonthlyTargetType" AS ENUM ('NONE', 'FIXED', 'LADDER');
-- CreateEnum
CREATE TYPE "RestrictionType" AS ENUM ('ACCOUNT_LIMIT', 'TOTAL_LIMIT');
-- CreateEnum
CREATE TYPE "ApprovalStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED');
-- CreateEnum
CREATE TYPE "OperationType" AS ENUM ('GRANT_AUTHORIZATION', 'REVOKE_AUTHORIZATION', 'GRANT_BYPASS', 'EXEMPT_PERCENTAGE', 'MODIFY_CONFIG');
-- CreateEnum
CREATE TYPE "RegionType" AS ENUM ('PROVINCE', 'CITY');
-- CreateEnum
CREATE TYPE "SystemAccountType" AS ENUM ('COST_ACCOUNT', 'OPERATION_ACCOUNT', 'HQ_COMMUNITY', 'RWAD_POOL_PENDING', 'SYSTEM_PROVINCE', 'SYSTEM_CITY');
-- CreateEnum
CREATE TYPE "SystemLedgerEntryType" AS ENUM ('PLANTING_ALLOCATION', 'REWARD_EXPIRED', 'TRANSFER_OUT', 'TRANSFER_IN', 'WITHDRAWAL', 'ADJUSTMENT');
-- CreateTable
CREATE TABLE "authorization_roles" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"role_type" "RoleType" NOT NULL,
"region_code" TEXT NOT NULL,
"region_name" TEXT NOT NULL,
"status" "AuthorizationStatus" NOT NULL DEFAULT 'PENDING',
"display_title" TEXT NOT NULL,
"authorized_at" TIMESTAMP(3),
"authorized_by" TEXT,
"revoked_at" TIMESTAMP(3),
"revoked_by" TEXT,
"revoke_reason" TEXT,
"initial_target_tree_count" INTEGER NOT NULL,
"monthly_target_type" "MonthlyTargetType" NOT NULL,
"require_local_percentage" DECIMAL(5,2) NOT NULL DEFAULT 5.0,
"exempt_from_percentage_check" BOOLEAN NOT NULL DEFAULT false,
"benefit_active" BOOLEAN NOT NULL DEFAULT false,
"benefit_activated_at" TIMESTAMP(3),
"benefit_deactivated_at" TIMESTAMP(3),
"current_month_index" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "authorization_roles_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "monthly_assessments" (
"id" TEXT NOT NULL,
"authorization_id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"role_type" "RoleType" NOT NULL,
"region_code" TEXT NOT NULL,
"assessment_month" TEXT NOT NULL,
"month_index" INTEGER NOT NULL,
"monthly_target" INTEGER NOT NULL,
"cumulative_target" INTEGER NOT NULL,
"monthly_completed" INTEGER NOT NULL DEFAULT 0,
"cumulative_completed" INTEGER NOT NULL DEFAULT 0,
"completed_at" TIMESTAMP(3),
"local_team_count" INTEGER NOT NULL DEFAULT 0,
"total_team_count" INTEGER NOT NULL DEFAULT 0,
"local_percentage" DECIMAL(5,2) NOT NULL DEFAULT 0,
"local_percentage_pass" BOOLEAN NOT NULL DEFAULT false,
"exceed_ratio" DECIMAL(10,4) NOT NULL DEFAULT 0,
"result" "AssessmentResult" NOT NULL DEFAULT 'NOT_ASSESSED',
"ranking_in_region" INTEGER,
"is_first_place" BOOLEAN NOT NULL DEFAULT false,
"is_bypassed" BOOLEAN NOT NULL DEFAULT false,
"bypassed_by" TEXT,
"bypassed_at" TIMESTAMP(3),
"assessed_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "monthly_assessments_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "monthly_bypasses" (
"id" TEXT NOT NULL,
"authorization_id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"role_type" "RoleType" NOT NULL,
"bypass_month" TEXT NOT NULL,
"granted_by" TEXT NOT NULL,
"granted_at" TIMESTAMP(3) NOT NULL,
"reason" TEXT,
"approver1_id" TEXT NOT NULL,
"approver1_at" TIMESTAMP(3) NOT NULL,
"approver2_id" TEXT,
"approver2_at" TIMESTAMP(3),
"approver3_id" TEXT,
"approver3_at" TIMESTAMP(3),
"approval_status" "ApprovalStatus" NOT NULL DEFAULT 'PENDING',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "monthly_bypasses_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ladder_target_configs" (
"id" TEXT NOT NULL,
"role_type" "RoleType" NOT NULL,
"month_index" INTEGER NOT NULL,
"monthly_target" INTEGER NOT NULL,
"cumulative_target" INTEGER NOT NULL,
"is_active" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ladder_target_configs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "planting_restrictions" (
"id" TEXT NOT NULL,
"restriction_type" "RestrictionType" NOT NULL,
"account_limit_days" INTEGER,
"account_limit_count" INTEGER,
"total_limit_days" INTEGER,
"total_limit_count" INTEGER,
"current_total_count" INTEGER NOT NULL DEFAULT 0,
"start_at" TIMESTAMP(3) NOT NULL,
"end_at" TIMESTAMP(3) NOT NULL,
"is_active" BOOLEAN NOT NULL DEFAULT true,
"created_by" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "planting_restrictions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "admin_approvals" (
"id" TEXT NOT NULL,
"operation_type" "OperationType" NOT NULL,
"target_id" TEXT NOT NULL,
"target_type" TEXT NOT NULL,
"request_data" JSONB NOT NULL,
"status" "ApprovalStatus" NOT NULL DEFAULT 'PENDING',
"requester_id" TEXT NOT NULL,
"approver1_id" TEXT,
"approver1_at" TIMESTAMP(3),
"approver2_id" TEXT,
"approver2_at" TIMESTAMP(3),
"approver3_id" TEXT,
"approver3_at" TIMESTAMP(3),
"completed_at" TIMESTAMP(3),
"rejected_by" TEXT,
"rejected_at" TIMESTAMP(3),
"reject_reason" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "admin_approvals_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "authorization_audit_logs" (
"id" TEXT NOT NULL,
"operation_type" TEXT NOT NULL,
"target_user_id" TEXT NOT NULL,
"target_role_type" "RoleType",
"target_region_code" TEXT,
"operator_id" TEXT NOT NULL,
"operator_role" TEXT NOT NULL,
"before_state" JSONB,
"after_state" JSONB,
"ip_address" TEXT,
"user_agent" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "authorization_audit_logs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "region_heat_maps" (
"id" TEXT NOT NULL,
"region_code" TEXT NOT NULL,
"region_name" TEXT NOT NULL,
"region_type" "RegionType" NOT NULL,
"total_plantings" INTEGER NOT NULL DEFAULT 0,
"monthly_plantings" INTEGER NOT NULL DEFAULT 0,
"weekly_plantings" INTEGER NOT NULL DEFAULT 0,
"daily_plantings" INTEGER NOT NULL DEFAULT 0,
"active_users" INTEGER NOT NULL DEFAULT 0,
"auth_company_count" INTEGER NOT NULL DEFAULT 0,
"heat_score" DECIMAL(10,2) NOT NULL DEFAULT 0,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "region_heat_maps_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "stickman_rankings" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"authorization_id" TEXT NOT NULL,
"role_type" "RoleType" NOT NULL,
"region_code" TEXT NOT NULL,
"region_name" TEXT NOT NULL,
"nickname" TEXT NOT NULL,
"avatar_url" TEXT,
"current_month" TEXT NOT NULL,
"cumulative_completed" INTEGER NOT NULL,
"cumulative_target" INTEGER NOT NULL,
"progress_percentage" DECIMAL(5,2) NOT NULL,
"exceed_ratio" DECIMAL(10,4) NOT NULL,
"ranking" INTEGER NOT NULL,
"is_first_place" BOOLEAN NOT NULL,
"monthly_reward_usdt" DECIMAL(18,2) NOT NULL,
"monthly_reward_rwad" DECIMAL(18,8) NOT NULL,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "stickman_rankings_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "authorization_configs" (
"id" TEXT NOT NULL,
"config_key" TEXT NOT NULL,
"config_value" TEXT NOT NULL,
"description" TEXT,
"is_active" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "authorization_configs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "system_accounts" (
"account_id" BIGSERIAL NOT NULL,
"account_type" "SystemAccountType" NOT NULL,
"region_code" VARCHAR(10),
"region_name" VARCHAR(50),
"wallet_address" VARCHAR(42),
"mpc_public_key" VARCHAR(130),
"usdt_balance" DECIMAL(20,8) NOT NULL DEFAULT 0,
"hashpower" DECIMAL(20,8) NOT NULL DEFAULT 0,
"total_received" DECIMAL(20,8) NOT NULL DEFAULT 0,
"total_transferred" DECIMAL(20,8) NOT NULL DEFAULT 0,
"status" VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "system_accounts_pkey" PRIMARY KEY ("account_id")
);
-- CreateTable
CREATE TABLE "system_account_ledgers" (
"ledger_id" BIGSERIAL NOT NULL,
"account_id" BIGINT NOT NULL,
"entry_type" "SystemLedgerEntryType" NOT NULL,
"amount" DECIMAL(20,8) NOT NULL,
"balance_after" DECIMAL(20,8) NOT NULL,
"source_order_id" BIGINT,
"source_reward_id" BIGINT,
"tx_hash" VARCHAR(66),
"memo" VARCHAR(500),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "system_account_ledgers_pkey" PRIMARY KEY ("ledger_id")
);
-- CreateIndex
CREATE INDEX "authorization_roles_user_id_idx" ON "authorization_roles"("user_id");
-- CreateIndex
CREATE INDEX "authorization_roles_role_type_region_code_idx" ON "authorization_roles"("role_type", "region_code");
-- CreateIndex
CREATE INDEX "authorization_roles_status_idx" ON "authorization_roles"("status");
-- CreateIndex
CREATE INDEX "authorization_roles_role_type_status_idx" ON "authorization_roles"("role_type", "status");
-- CreateIndex
CREATE UNIQUE INDEX "authorization_roles_user_id_role_type_region_code_key" ON "authorization_roles"("user_id", "role_type", "region_code");
-- CreateIndex
CREATE INDEX "monthly_assessments_user_id_assessment_month_idx" ON "monthly_assessments"("user_id", "assessment_month");
-- CreateIndex
CREATE INDEX "monthly_assessments_role_type_region_code_assessment_month_idx" ON "monthly_assessments"("role_type", "region_code", "assessment_month");
-- CreateIndex
CREATE INDEX "monthly_assessments_assessment_month_result_idx" ON "monthly_assessments"("assessment_month", "result");
-- CreateIndex
CREATE INDEX "monthly_assessments_assessment_month_role_type_exceed_ratio_idx" ON "monthly_assessments"("assessment_month", "role_type", "exceed_ratio" DESC);
-- CreateIndex
CREATE UNIQUE INDEX "monthly_assessments_authorization_id_assessment_month_key" ON "monthly_assessments"("authorization_id", "assessment_month");
-- CreateIndex
CREATE INDEX "monthly_bypasses_user_id_bypass_month_idx" ON "monthly_bypasses"("user_id", "bypass_month");
-- CreateIndex
CREATE UNIQUE INDEX "monthly_bypasses_authorization_id_bypass_month_key" ON "monthly_bypasses"("authorization_id", "bypass_month");
-- CreateIndex
CREATE UNIQUE INDEX "ladder_target_configs_role_type_month_index_key" ON "ladder_target_configs"("role_type", "month_index");
-- CreateIndex
CREATE INDEX "admin_approvals_status_idx" ON "admin_approvals"("status");
-- CreateIndex
CREATE INDEX "admin_approvals_target_id_target_type_idx" ON "admin_approvals"("target_id", "target_type");
-- CreateIndex
CREATE INDEX "authorization_audit_logs_target_user_id_idx" ON "authorization_audit_logs"("target_user_id");
-- CreateIndex
CREATE INDEX "authorization_audit_logs_operator_id_idx" ON "authorization_audit_logs"("operator_id");
-- CreateIndex
CREATE INDEX "authorization_audit_logs_created_at_idx" ON "authorization_audit_logs"("created_at");
-- CreateIndex
CREATE UNIQUE INDEX "region_heat_maps_region_code_region_type_key" ON "region_heat_maps"("region_code", "region_type");
-- CreateIndex
CREATE INDEX "stickman_rankings_role_type_region_code_current_month_idx" ON "stickman_rankings"("role_type", "region_code", "current_month");
-- CreateIndex
CREATE UNIQUE INDEX "stickman_rankings_authorization_id_current_month_key" ON "stickman_rankings"("authorization_id", "current_month");
-- CreateIndex
CREATE UNIQUE INDEX "authorization_configs_config_key_key" ON "authorization_configs"("config_key");
-- CreateIndex
CREATE INDEX "idx_system_account_type" ON "system_accounts"("account_type");
-- CreateIndex
CREATE INDEX "idx_system_wallet_address" ON "system_accounts"("wallet_address");
-- CreateIndex
CREATE UNIQUE INDEX "system_accounts_account_type_region_code_key" ON "system_accounts"("account_type", "region_code");
-- CreateIndex
CREATE INDEX "idx_system_ledger_account_created" ON "system_account_ledgers"("account_id", "created_at" DESC);
-- CreateIndex
CREATE INDEX "idx_system_ledger_source_order" ON "system_account_ledgers"("source_order_id");
-- CreateIndex
CREATE INDEX "idx_system_ledger_tx_hash" ON "system_account_ledgers"("tx_hash");
-- AddForeignKey
ALTER TABLE "monthly_assessments" ADD CONSTRAINT "monthly_assessments_authorization_id_fkey" FOREIGN KEY ("authorization_id") REFERENCES "authorization_roles"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "monthly_bypasses" ADD CONSTRAINT "monthly_bypasses_authorization_id_fkey" FOREIGN KEY ("authorization_id") REFERENCES "authorization_roles"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "system_account_ledgers" ADD CONSTRAINT "system_account_ledgers_account_id_fkey" FOREIGN KEY ("account_id") REFERENCES "system_accounts"("account_id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -4,7 +4,13 @@ import { ExtractJwt, Strategy } from 'passport-jwt'
import { ConfigService } from '@nestjs/config'
export interface JwtPayload {
sub: string
// Identity-service uses 'userId' field
userId: string
accountSequence?: number
deviceId?: string
type?: string
// Legacy support for 'sub' field
sub?: string
walletAddress?: string
roles?: string[]
iat?: number
@ -22,8 +28,12 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
}
async validate(payload: JwtPayload) {
// Support both 'userId' (from identity-service) and 'sub' (legacy)
const userId = payload.userId || payload.sub
return {
userId: payload.sub,
userId,
accountSequence: payload.accountSequence,
deviceId: payload.deviceId,
walletAddress: payload.walletAddress,
roles: payload.roles,
}

View File

@ -67,11 +67,17 @@ class ApiEndpoints {
// Community & Referral (-> Referral Service)
static const String community = '$apiPrefix/community';
static const String referral = '$apiPrefix/referral';
static const String referralMe = '$referral/me'; //
static const String referralDirects = '$referral/me/direct-referrals'; //
static const String referralList = '$community/referrals';
static const String earnings = '$community/earnings';
static const String claimEarnings = '$community/claim';
static const String generateReferralLink = '$referral/generate-link';
// Authorization (-> Authorization Service)
static const String authorizations = '$apiPrefix/authorizations';
static const String myAuthorizations = '$authorizations/my'; //
// Telemetry (-> Reporting Service)
static const String telemetry = '$apiPrefix/telemetry';
static const String telemetrySession = '$telemetry/session';

View File

@ -4,6 +4,7 @@ import '../storage/local_storage.dart';
import '../network/api_client.dart';
import '../services/account_service.dart';
import '../services/referral_service.dart';
import '../services/authorization_service.dart';
import '../services/deposit_service.dart';
// Storage Providers
@ -37,6 +38,12 @@ final referralServiceProvider = Provider<ReferralService>((ref) {
return ReferralService(apiClient: apiClient);
});
// Authorization Service Provider
final authorizationServiceProvider = Provider<AuthorizationService>((ref) {
final apiClient = ref.watch(apiClientProvider);
return AuthorizationService(apiClient: apiClient);
});
// Deposit Service Provider
final depositServiceProvider = Provider<DepositService>((ref) {
final apiClient = ref.watch(apiClientProvider);

View File

@ -0,0 +1,245 @@
import 'package:flutter/foundation.dart';
import '../network/api_client.dart';
import '../constants/api_endpoints.dart';
///
enum RoleType {
community,
provinceCompany,
cityCompany,
}
extension RoleTypeExtension on RoleType {
String get value {
switch (this) {
case RoleType.community:
return 'COMMUNITY';
case RoleType.provinceCompany:
return 'PROVINCE_COMPANY';
case RoleType.cityCompany:
return 'CITY_COMPANY';
}
}
String get displayName {
switch (this) {
case RoleType.community:
return '社区';
case RoleType.provinceCompany:
return '省公司';
case RoleType.cityCompany:
return '市公司';
}
}
static RoleType? fromString(String? value) {
if (value == null) return null;
switch (value.toUpperCase()) {
case 'COMMUNITY':
return RoleType.community;
case 'PROVINCE_COMPANY':
return RoleType.provinceCompany;
case 'CITY_COMPANY':
return RoleType.cityCompany;
default:
return null;
}
}
}
///
enum AuthorizationStatus {
pending,
active,
suspended,
revoked,
}
extension AuthorizationStatusExtension on AuthorizationStatus {
String get value {
switch (this) {
case AuthorizationStatus.pending:
return 'PENDING';
case AuthorizationStatus.active:
return 'ACTIVE';
case AuthorizationStatus.suspended:
return 'SUSPENDED';
case AuthorizationStatus.revoked:
return 'REVOKED';
}
}
static AuthorizationStatus fromString(String? value) {
switch (value?.toUpperCase()) {
case 'PENDING':
return AuthorizationStatus.pending;
case 'ACTIVE':
return AuthorizationStatus.active;
case 'SUSPENDED':
return AuthorizationStatus.suspended;
case 'REVOKED':
return AuthorizationStatus.revoked;
default:
return AuthorizationStatus.pending;
}
}
}
///
class AuthorizationResponse {
final String authorizationId;
final String userId;
final RoleType roleType;
final String regionCode;
final String regionName;
final AuthorizationStatus status;
final String displayTitle;
final bool benefitActive;
final int currentMonthIndex;
final double requireLocalPercentage;
final bool exemptFromPercentageCheck;
final DateTime createdAt;
final DateTime updatedAt;
AuthorizationResponse({
required this.authorizationId,
required this.userId,
required this.roleType,
required this.regionCode,
required this.regionName,
required this.status,
required this.displayTitle,
required this.benefitActive,
required this.currentMonthIndex,
required this.requireLocalPercentage,
required this.exemptFromPercentageCheck,
required this.createdAt,
required this.updatedAt,
});
factory AuthorizationResponse.fromJson(Map<String, dynamic> json) {
return AuthorizationResponse(
authorizationId: json['authorizationId']?.toString() ?? '',
userId: json['userId']?.toString() ?? '',
roleType: RoleTypeExtension.fromString(json['roleType']) ?? RoleType.community,
regionCode: json['regionCode'] ?? '',
regionName: json['regionName'] ?? '',
status: AuthorizationStatusExtension.fromString(json['status']),
displayTitle: json['displayTitle'] ?? '',
benefitActive: json['benefitActive'] ?? false,
currentMonthIndex: json['currentMonthIndex'] ?? 0,
requireLocalPercentage: (json['requireLocalPercentage'] ?? 0).toDouble(),
exemptFromPercentageCheck: json['exemptFromPercentageCheck'] ?? false,
createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt'])
: DateTime.now(),
updatedAt: json['updatedAt'] != null
? DateTime.parse(json['updatedAt'])
: DateTime.now(),
);
}
}
///
class UserAuthorizationSummary {
/// ()
final AuthorizationResponse? community;
/// ()
final AuthorizationResponse? provinceCompany;
/// ()
final AuthorizationResponse? cityCompany;
///
final List<AuthorizationResponse> allAuthorizations;
UserAuthorizationSummary({
this.community,
this.provinceCompany,
this.cityCompany,
required this.allAuthorizations,
});
///
bool get hasAnyAuthorization => allAuthorizations.isNotEmpty;
///
String? get communityName => community?.displayTitle;
///
String? get provinceCompanyName => provinceCompany?.displayTitle;
///
String? get cityCompanyName => cityCompany?.displayTitle;
factory UserAuthorizationSummary.fromList(List<AuthorizationResponse> authorizations) {
AuthorizationResponse? community;
AuthorizationResponse? provinceCompany;
AuthorizationResponse? cityCompany;
for (final auth in authorizations) {
switch (auth.roleType) {
case RoleType.community:
community = auth;
break;
case RoleType.provinceCompany:
provinceCompany = auth;
break;
case RoleType.cityCompany:
cityCompany = auth;
break;
}
}
return UserAuthorizationSummary(
community: community,
provinceCompany: provinceCompany,
cityCompany: cityCompany,
allAuthorizations: authorizations,
);
}
}
///
///
/// :
/// -
/// - //
class AuthorizationService {
final ApiClient _apiClient;
AuthorizationService({required ApiClient apiClient}) : _apiClient = apiClient;
///
///
/// GET /authorizations/my (authorization-service)
Future<List<AuthorizationResponse>> getMyAuthorizations() async {
try {
debugPrint('获取授权列表...');
final response = await _apiClient.get(ApiEndpoints.myAuthorizations);
if (response.statusCode == 200) {
final data = response.data;
if (data is List) {
final authorizations = data
.map((e) => AuthorizationResponse.fromJson(e as Map<String, dynamic>))
.toList();
debugPrint('授权列表获取成功: ${authorizations.length} 个授权');
return authorizations;
}
return [];
}
throw Exception('获取授权列表失败');
} catch (e) {
debugPrint('获取授权列表失败: $e');
rethrow;
}
}
///
///
/// 便访//
Future<UserAuthorizationSummary> getMyAuthorizationSummary() async {
final authorizations = await getMyAuthorizations();
return UserAuthorizationSummary.fromList(authorizations);
}
}

View File

@ -1,5 +1,102 @@
import 'package:flutter/foundation.dart';
import '../network/api_client.dart';
import '../constants/api_endpoints.dart';
/// ( referral-service)
class ReferralInfoResponse {
final String userId;
final String referralCode;
final String? referrerId;
final int referralChainDepth;
final int directReferralCount;
final int totalTeamCount;
final int personalPlantingCount;
final int teamPlantingCount;
final double leaderboardScore;
final int? leaderboardRank;
final DateTime createdAt;
ReferralInfoResponse({
required this.userId,
required this.referralCode,
this.referrerId,
required this.referralChainDepth,
required this.directReferralCount,
required this.totalTeamCount,
required this.personalPlantingCount,
required this.teamPlantingCount,
required this.leaderboardScore,
this.leaderboardRank,
required this.createdAt,
});
factory ReferralInfoResponse.fromJson(Map<String, dynamic> json) {
return ReferralInfoResponse(
userId: json['userId']?.toString() ?? '',
referralCode: json['referralCode'] ?? '',
referrerId: json['referrerId']?.toString(),
referralChainDepth: json['referralChainDepth'] ?? 0,
directReferralCount: json['directReferralCount'] ?? 0,
totalTeamCount: json['totalTeamCount'] ?? 0,
personalPlantingCount: json['personalPlantingCount'] ?? 0,
teamPlantingCount: json['teamPlantingCount'] ?? 0,
leaderboardScore: (json['leaderboardScore'] ?? 0).toDouble(),
leaderboardRank: json['leaderboardRank'],
createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt'])
: DateTime.now(),
);
}
}
///
class DirectReferralInfo {
final String userId;
final String referralCode;
final int teamCount;
final DateTime joinedAt;
DirectReferralInfo({
required this.userId,
required this.referralCode,
required this.teamCount,
required this.joinedAt,
});
factory DirectReferralInfo.fromJson(Map<String, dynamic> json) {
return DirectReferralInfo(
userId: json['userId']?.toString() ?? '',
referralCode: json['referralCode'] ?? '',
teamCount: json['teamCount'] ?? 0,
joinedAt: json['joinedAt'] != null
? DateTime.parse(json['joinedAt'])
: DateTime.now(),
);
}
}
///
class DirectReferralsResponse {
final List<DirectReferralInfo> referrals;
final int total;
final bool hasMore;
DirectReferralsResponse({
required this.referrals,
required this.total,
required this.hasMore,
});
factory DirectReferralsResponse.fromJson(Map<String, dynamic> json) {
return DirectReferralsResponse(
referrals: (json['referrals'] as List? ?? [])
.map((e) => DirectReferralInfo.fromJson(e))
.toList(),
total: json['total'] ?? 0,
hasMore: json['hasMore'] ?? false,
);
}
}
///
class ReferralLinkResponse {
@ -7,6 +104,7 @@ class ReferralLinkResponse {
final String referralCode;
final String shortUrl;
final String fullUrl;
final String downloadUrl; // APK
final String? channel;
final String? campaignId;
final DateTime createdAt;
@ -16,6 +114,7 @@ class ReferralLinkResponse {
required this.referralCode,
required this.shortUrl,
required this.fullUrl,
required this.downloadUrl,
this.channel,
this.campaignId,
required this.createdAt,
@ -27,6 +126,7 @@ class ReferralLinkResponse {
referralCode: json['referralCode'] ?? '',
shortUrl: json['shortUrl'] ?? '',
fullUrl: json['fullUrl'] ?? '',
downloadUrl: json['downloadUrl'] ?? 'https://s3.szaiai.com/rwadurian/app-release.apk',
channel: json['channel'],
campaignId: json['campaignId'],
createdAt: json['createdAt'] != null
@ -104,6 +204,7 @@ class WalletAddress {
/// :
/// -
/// - ()
/// -
class ReferralService {
final ApiClient _apiClient;
@ -111,11 +212,11 @@ class ReferralService {
/// ()
///
/// GET /me
/// GET /me (identity-service)
Future<MeResponse> getMe() async {
try {
debugPrint('获取用户信息...');
final response = await _apiClient.get('/me');
final response = await _apiClient.get(ApiEndpoints.me);
if (response.statusCode == 200) {
final data = response.data as Map<String, dynamic>;
@ -130,6 +231,57 @@ class ReferralService {
}
}
///
///
/// GET /referral/me (referral-service)
Future<ReferralInfoResponse> getMyReferralInfo() async {
try {
debugPrint('获取推荐信息...');
final response = await _apiClient.get(ApiEndpoints.referralMe);
if (response.statusCode == 200) {
final data = response.data as Map<String, dynamic>;
debugPrint('推荐信息获取成功: directReferralCount=${data['directReferralCount']}');
return ReferralInfoResponse.fromJson(data);
}
throw Exception('获取推荐信息失败');
} catch (e) {
debugPrint('获取推荐信息失败: $e');
rethrow;
}
}
///
///
/// GET /referral/me/direct-referrals (referral-service)
Future<DirectReferralsResponse> getDirectReferrals({
int limit = 50,
int offset = 0,
}) async {
try {
debugPrint('获取直推列表...');
final response = await _apiClient.get(
ApiEndpoints.referralDirects,
queryParameters: {
'limit': limit,
'offset': offset,
},
);
if (response.statusCode == 200) {
final data = response.data as Map<String, dynamic>;
debugPrint('直推列表获取成功: total=${data['total']}');
return DirectReferralsResponse.fromJson(data);
}
throw Exception('获取直推列表失败');
} catch (e) {
debugPrint('获取直推列表失败: $e');
rethrow;
}
}
/// ()
///
/// POST /referrals/links

View File

@ -1,4 +1,5 @@
import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -7,9 +8,9 @@ import 'package:go_router/go_router.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:device_info_plus/device_info_plus.dart';
import '../../../../core/di/injection_container.dart';
import '../../../../core/services/referral_service.dart';
import '../../../../routes/route_paths.dart';
import '../../../../routes/app_router.dart';
import 'dart:async';
/// -
///
@ -29,23 +30,25 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
String? _localAvatarPath; //
String _referrerSerial = '--'; // API获取
String _referralCode = '--'; //
final String _community = '星空社区';
final String _parentCommunity = '银河社区';
final String _childCommunity = '星辰社区';
final String _cityCompany = '深圳公司';
final String _provinceCompany = '广东公司';
final String _province = '广东';
//
final int _teamUsers = 1204;
final int _teamPlanting = 8930;
// authorization-service
String _community = '--';
String _parentCommunity = '--';
String _childCommunity = '--';
String _cityCompany = '--';
String _provinceCompany = '--';
String _province = '--';
//
final List<Map<String, dynamic>> _referrals = [
{'serial': '87654321', 'personal': 15, 'team': 120},
{'serial': '87654322', 'personal': 10, 'team': 85},
{'serial': '87654323', 'personal': 25, 'team': 250},
];
// referral-service
int _directReferralCount = 0;
int _totalTeamCount = 0;
int _personalPlantingCount = 0;
int _teamPlantingCount = 0;
double _leaderboardScore = 0;
int? _leaderboardRank;
// referral-service
List<Map<String, dynamic>> _referrals = [];
//
final int _communityLevel = 3;
@ -80,6 +83,9 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
_checkLocalAvatarSync();
_loadUserData();
_loadAppInfo();
//
_loadReferralData();
_loadAuthorizationData();
}
///
@ -200,6 +206,64 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
}
}
/// (from referral-service)
Future<void> _loadReferralData() async {
try {
final referralService = ref.read(referralServiceProvider);
//
final results = await Future.wait([
referralService.getMyReferralInfo(),
referralService.getDirectReferrals(limit: 10),
]);
final referralInfo = results[0] as ReferralInfoResponse;
final directReferrals = results[1] as DirectReferralsResponse;
if (mounted) {
setState(() {
_directReferralCount = referralInfo.directReferralCount;
_totalTeamCount = referralInfo.totalTeamCount;
_personalPlantingCount = referralInfo.personalPlantingCount;
_teamPlantingCount = referralInfo.teamPlantingCount;
_leaderboardScore = referralInfo.leaderboardScore;
_leaderboardRank = referralInfo.leaderboardRank;
//
_referrals = directReferrals.referrals.map((r) => {
'serial': r.userId,
'personal': 0, // API暂未返回个人认种量
'team': r.teamCount,
}).toList();
});
}
} catch (e) {
debugPrint('[ProfilePage] 加载推荐数据失败: $e');
//
}
}
/// (from authorization-service)
Future<void> _loadAuthorizationData() async {
try {
final authorizationService = ref.read(authorizationServiceProvider);
final summary = await authorizationService.getMyAuthorizationSummary();
if (mounted) {
setState(() {
_community = summary.communityName ?? '--';
_cityCompany = summary.cityCompanyName ?? '--';
_provinceCompany = summary.provinceCompanyName ?? '--';
// API获取
// API或从推荐链中计算
});
}
} catch (e) {
debugPrint('[ProfilePage] 加载授权数据失败: $e');
//
}
}
///
Future<void> _downloadAndCacheAvatar(String url) async {
final accountService = ref.read(accountServiceProvider);
@ -1100,7 +1164,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
child: Column(
children: [
const Text(
'团队注册用户',
'直推人数',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
@ -1112,7 +1176,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
),
const SizedBox(height: 8),
Text(
_formatInt(_teamUsers),
_formatInt(_directReferralCount),
style: const TextStyle(
fontSize: 24,
fontFamily: 'Inter',
@ -1153,7 +1217,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
),
const SizedBox(height: 8),
Text(
_formatInt(_teamPlanting),
_formatInt(_totalTeamCount),
style: const TextStyle(
fontSize: 24,
fontFamily: 'Inter',