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:
parent
15f617c9a9
commit
d0487c4a7e
|
|
@ -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 - 全局插件配置
|
||||
# =============================================================================
|
||||
|
|
|
|||
|
|
@ -43,5 +43,4 @@ lerna-debug.log*
|
|||
!.env.example
|
||||
|
||||
# Prisma
|
||||
prisma/migrations/*
|
||||
!prisma/migrations/.gitkeep
|
||||
prisma/migrations/**/migration_lock.toml
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in New Issue