first commit

This commit is contained in:
CN-JS-HuiBai
2026-04-07 16:54:24 +08:00
commit 2c6a38c80d
399 changed files with 42205 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Models\Payment;
use App\Utils\Dict;
use Illuminate\Http\Request;
class CommController extends Controller
{
public function config()
{
$data = [
'is_telegram' => (int)admin_setting('telegram_bot_enable', 0),
'telegram_discuss_link' => admin_setting('telegram_discuss_link'),
'stripe_pk' => admin_setting('stripe_pk_live'),
'withdraw_methods' => admin_setting('commission_withdraw_method', Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT),
'withdraw_close' => (int)admin_setting('withdraw_close_enable', 0),
'currency' => admin_setting('currency', 'CNY'),
'currency_symbol' => admin_setting('currency_symbol', '¥'),
'commission_distribution_enable' => (int)admin_setting('commission_distribution_enable', 0),
'commission_distribution_l1' => admin_setting('commission_distribution_l1'),
'commission_distribution_l2' => admin_setting('commission_distribution_l2'),
'commission_distribution_l3' => admin_setting('commission_distribution_l3')
];
return $this->success($data);
}
public function getStripePublicKey(Request $request)
{
$payment = Payment::where('id', $request->input('id'))
->where('payment', 'StripeCredit')
->first();
if (!$payment) throw new ApiException('payment is not found');
return $this->success($payment->config['stripe_pk_live']);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Http\Resources\CouponResource;
use App\Services\CouponService;
use Illuminate\Http\Request;
class CouponController extends Controller
{
public function check(Request $request)
{
if (empty($request->input('code'))) {
return $this->fail([422, __('Coupon cannot be empty')]);
}
$couponService = new CouponService($request->input('code'));
$couponService->setPlanId($request->input('plan_id'));
$couponService->setUserId($request->user()->id);
$couponService->setPeriod($request->input('period'));
$couponService->check();
return $this->success(CouponResource::make($couponService->getCoupon()));
}
}

View File

@@ -0,0 +1,193 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\GiftCardCheckRequest;
use App\Http\Requests\User\GiftCardRedeemRequest;
use App\Models\GiftCardUsage;
use App\Services\GiftCardService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class GiftCardController extends Controller
{
/**
* 查询兑换码信息
*/
public function check(GiftCardCheckRequest $request)
{
try {
$giftCardService = new GiftCardService($request->input('code'));
$giftCardService->setUser($request->user());
// 1. 验证礼品卡本身是否有效 (如不存在、已过期、已禁用)
$giftCardService->validateIsActive();
// 2. 检查用户是否满足使用条件,但不在此处抛出异常
$eligibility = $giftCardService->checkUserEligibility();
// 3. 获取卡片信息和奖励预览
$codeInfo = $giftCardService->getCodeInfo();
$rewardPreview = $giftCardService->previewRewards();
return $this->success([
'code_info' => $codeInfo, // 这里面已经包含 plan_info
'reward_preview' => $rewardPreview,
'can_redeem' => $eligibility['can_redeem'],
'reason' => $eligibility['reason'],
]);
} catch (ApiException $e) {
// 这里只捕获 validateIsActive 抛出的异常
return $this->fail([400, $e->getMessage()]);
} catch (\Exception $e) {
Log::error('礼品卡查询失败', [
'code' => $request->input('code'),
'user_id' => $request->user()->id,
'error' => $e->getMessage(),
]);
return $this->fail([500, '查询失败,请稍后重试']);
}
}
/**
* 使用兑换码
*/
public function redeem(GiftCardRedeemRequest $request)
{
try {
$giftCardService = new GiftCardService($request->input('code'));
$giftCardService->setUser($request->user());
$giftCardService->validate();
// 使用礼品卡
$result = $giftCardService->redeem([
// 'ip_address' => $request->ip(),
'user_agent' => $request->userAgent(),
]);
Log::info('礼品卡使用成功', [
'code' => $request->input('code'),
'user_id' => $request->user()->id,
'rewards' => $result['rewards'],
]);
return $this->success([
'message' => '兑换成功!',
'rewards' => $result['rewards'],
'invite_rewards' => $result['invite_rewards'],
'template_name' => $result['template_name'],
]);
} catch (ApiException $e) {
return $this->fail([400, $e->getMessage()]);
} catch (\Exception $e) {
Log::error('礼品卡使用失败', [
'code' => $request->input('code'),
'user_id' => $request->user()->id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return $this->fail([500, '兑换失败,请稍后重试']);
}
}
/**
* 获取用户兑换记录
*/
public function history(Request $request)
{
$request->validate([
'page' => 'integer|min:1',
'per_page' => 'integer|min:1|max:100',
]);
$perPage = $request->input('per_page', 15);
$usages = GiftCardUsage::with(['template', 'code'])
->where('user_id', $request->user()->id)
->orderBy('created_at', 'desc')
->paginate($perPage);
$data = $usages->getCollection()->map(function (GiftCardUsage $usage) {
return [
'id' => $usage->id,
'code' => ($usage->code instanceof \App\Models\GiftCardCode && $usage->code->code)
? (substr($usage->code->code, 0, 8) . '****')
: '',
'template_name' => $usage->template->name ?? '',
'template_type' => $usage->template->type ?? '',
'template_type_name' => $usage->template->type_name ?? '',
'rewards_given' => $usage->rewards_given,
'invite_rewards' => $usage->invite_rewards,
'multiplier_applied' => $usage->multiplier_applied,
'created_at' => $usage->created_at,
];
})->values();
return response()->json([
'data' => $data,
'pagination' => [
'current_page' => $usages->currentPage(),
'last_page' => $usages->lastPage(),
'per_page' => $usages->perPage(),
'total' => $usages->total(),
],
]);
}
/**
* 获取兑换记录详情
*/
public function detail(Request $request)
{
$request->validate([
'id' => 'required|integer|exists:v2_gift_card_usage,id',
]);
$usage = GiftCardUsage::with(['template', 'code', 'inviteUser'])
->where('user_id', $request->user()->id)
->where('id', $request->input('id'))
->first();
if (!$usage) {
return $this->fail([404, '记录不存在']);
}
return $this->success([
'id' => $usage->id,
'code' => $usage->code->code ?? '',
'template' => [
'name' => $usage->template->name ?? '',
'description' => $usage->template->description ?? '',
'type' => $usage->template->type ?? '',
'type_name' => $usage->template->type_name ?? '',
'icon' => $usage->template->icon ?? '',
'theme_color' => $usage->template->theme_color ?? '',
],
'rewards_given' => $usage->rewards_given,
'invite_rewards' => $usage->invite_rewards,
'invite_user' => $usage->inviteUser ? [
'id' => $usage->inviteUser->id ?? '',
'email' => isset($usage->inviteUser->email) ? (substr($usage->inviteUser->email, 0, 3) . '***@***') : '',
] : null,
'user_level_at_use' => $usage->user_level_at_use,
'plan_id_at_use' => $usage->plan_id_at_use,
'multiplier_applied' => $usage->multiplier_applied,
// 'ip_address' => $usage->ip_address,
'notes' => $usage->notes,
'created_at' => $usage->created_at,
]);
}
/**
* 获取可用的礼品卡类型
*/
public function types(Request $request)
{
return $this->success([
'types' => \App\Models\GiftCardTemplate::getTypeMap(),
]);
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Http\Resources\ComissionLogResource;
use App\Http\Resources\InviteCodeResource;
use App\Models\CommissionLog;
use App\Models\InviteCode;
use App\Models\Order;
use App\Models\User;
use App\Utils\Helper;
use Illuminate\Http\Request;
class InviteController extends Controller
{
public function save(Request $request)
{
if (InviteCode::where('user_id', $request->user()->id)->where('status', 0)->count() >= admin_setting('invite_gen_limit', 5)) {
return $this->fail([400,__('The maximum number of creations has been reached')]);
}
$inviteCode = new InviteCode();
$inviteCode->user_id = $request->user()->id;
$inviteCode->code = Helper::randomChar(8);
return $this->success($inviteCode->save());
}
public function details(Request $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('page_size') >= 10 ? $request->input('page_size') : 10;
$builder = CommissionLog::where('invite_user_id', $request->user()->id)
->where('get_amount', '>', 0)
->orderBy('created_at', 'DESC');
$total = $builder->count();
$details = $builder->forPage($current, $pageSize)
->get();
return response([
'data' => ComissionLogResource::collection($details),
'total' => $total
]);
}
public function fetch(Request $request)
{
$commission_rate = admin_setting('invite_commission', 10);
$user = User::find($request->user()->id)
->load(['codes' => fn($query) => $query->where('status', 0)]);
if ($user->commission_rate) {
$commission_rate = $user->commission_rate;
}
$uncheck_commission_balance = (int)Order::where('status', 3)
->where('commission_status', 0)
->where('invite_user_id', $user->id)
->sum('commission_balance');
if (admin_setting('commission_distribution_enable', 0)) {
$uncheck_commission_balance = $uncheck_commission_balance * (admin_setting('commission_distribution_l1') / 100);
}
$stat = [
//已注册用户数
(int)User::where('invite_user_id', $user->id)->count(),
//有效的佣金
(int)CommissionLog::where('invite_user_id', $user->id)
->sum('get_amount'),
//确认中的佣金
$uncheck_commission_balance,
//佣金比例
(int)$commission_rate,
//可用佣金
(int)$user->commission_balance
];
$data = [
'codes' => InviteCodeResource::collection($user->codes),
'stat' => $stat
];
return $this->success($data);
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Http\Resources\KnowledgeResource;
use App\Models\Knowledge;
use App\Models\User;
use App\Services\Plugin\HookManager;
use App\Services\UserService;
use App\Utils\Helper;
use Illuminate\Http\Request;
class KnowledgeController extends Controller
{
private UserService $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function fetch(Request $request)
{
$request->validate([
'id' => 'nullable|sometimes|integer|min:1',
'language' => 'nullable|sometimes|string|max:10',
'keyword' => 'nullable|sometimes|string|max:255',
]);
return $request->input('id')
? $this->fetchSingle($request)
: $this->fetchList($request);
}
private function fetchSingle(Request $request)
{
$knowledge = $this->buildKnowledgeQuery()
->where('id', $request->input('id'))
->first();
if (!$knowledge) {
return $this->fail([500, __('Article does not exist')]);
}
$knowledge = $knowledge->toArray();
$knowledge = $this->processKnowledgeContent($knowledge, $request->user());
return $this->success(KnowledgeResource::make($knowledge));
}
private function fetchList(Request $request)
{
$builder = $this->buildKnowledgeQuery(['id', 'category', 'title', 'updated_at', 'body'])
->where('language', $request->input('language'))
->orderBy('sort', 'ASC');
$keyword = $request->input('keyword');
if ($keyword) {
$builder = $builder->where(function ($query) use ($keyword) {
$query->where('title', 'LIKE', "%{$keyword}%")
->orWhere('body', 'LIKE', "%{$keyword}%");
});
}
$knowledges = $builder->get()
->map(function ($knowledge) use ($request) {
$knowledge = $knowledge->toArray();
$knowledge = $this->processKnowledgeContent($knowledge, $request->user());
return KnowledgeResource::make($knowledge);
})
->groupBy('category');
return $this->success($knowledges);
}
private function buildKnowledgeQuery(array $select = ['*'])
{
return Knowledge::select($select)->where('show', 1);
}
private function processKnowledgeContent(array $knowledge, User $user): array
{
if (!isset($knowledge['body'])) {
return $knowledge;
}
if (!$this->userService->isAvailable($user)) {
$this->formatAccessData($knowledge['body']);
}
$subscribeUrl = Helper::getSubscribeUrl($user['token']);
$knowledge['body'] = $this->replacePlaceholders($knowledge['body'], $subscribeUrl);
return $knowledge;
}
private function formatAccessData(&$body): void
{
$rules = [
[
'type' => 'regex',
'pattern' => '/<!--access start-->(.*?)<!--access end-->/s',
'replacement' => '<div class="v2board-no-access">' . __('You must have a valid subscription to view content in this area') . '</div>'
]
];
$this->applyReplacementRules($body, $rules);
}
private function replacePlaceholders(string $body, string $subscribeUrl): string
{
$rules = [
[
'type' => 'string',
'search' => '{{siteName}}',
'replacement' => admin_setting('app_name', 'XBoard')
],
[
'type' => 'string',
'search' => '{{subscribeUrl}}',
'replacement' => $subscribeUrl
],
[
'type' => 'string',
'search' => '{{urlEncodeSubscribeUrl}}',
'replacement' => urlencode($subscribeUrl)
],
[
'type' => 'string',
'search' => '{{safeBase64SubscribeUrl}}',
'replacement' => str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($subscribeUrl))
]
];
$this->applyReplacementRules($body, $rules);
return $body;
}
private function applyReplacementRules(string &$body, array $rules): void
{
foreach ($rules as $rule) {
if ($rule['type'] === 'regex') {
$body = preg_replace($rule['pattern'], $rule['replacement'], $body);
} else {
$body = str_replace($rule['search'], $rule['replacement'], $body);
}
}
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Models\Notice;
use Illuminate\Http\Request;
class NoticeController extends Controller
{
public function fetch(Request $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = 5;
$model = Notice::orderBy('sort', 'ASC')
->orderBy('id', 'DESC')
->where('show', true);
$total = $model->count();
$res = $model->forPage($current, $pageSize)
->get();
return response([
'data' => $res,
'total' => $total
]);
}
}

View File

@@ -0,0 +1,212 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\OrderSave;
use App\Http\Resources\OrderResource;
use App\Models\Order;
use App\Models\Payment;
use App\Models\Plan;
use App\Models\User;
use App\Services\CouponService;
use App\Services\OrderService;
use App\Services\PaymentService;
use App\Services\PlanService;
use App\Services\UserService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class OrderController extends Controller
{
public function fetch(Request $request)
{
$request->validate([
'status' => 'nullable|integer|in:0,1,2,3',
]);
$orders = Order::with('plan')
->where('user_id', $request->user()->id)
->when($request->input('status') !== null, function ($query) use ($request) {
$query->where('status', $request->input('status'));
})
->orderBy('created_at', 'DESC')
->get();
return $this->success(OrderResource::collection($orders));
}
public function detail(Request $request)
{
$request->validate([
'trade_no' => 'required|string',
]);
$order = Order::with(['payment', 'plan'])
->where('user_id', $request->user()->id)
->where('trade_no', $request->input('trade_no'))
->first();
if (!$order) {
return $this->fail([400, __('Order does not exist or has been paid')]);
}
$order['try_out_plan_id'] = (int) admin_setting('try_out_plan_id');
if (!$order->plan) {
return $this->fail([400, __('Subscription plan does not exist')]);
}
if ($order->surplus_order_ids) {
$order['surplus_orders'] = Order::whereIn('id', $order->surplus_order_ids)->get();
}
return $this->success(OrderResource::make($order));
}
public function save(OrderSave $request)
{
$request->validate([
'plan_id' => 'required|exists:App\Models\Plan,id',
'period' => 'required|string'
]);
$user = User::findOrFail($request->user()->id);
$userService = app(UserService::class);
if ($userService->isNotCompleteOrderByUserId($user->id)) {
throw new ApiException(__('You have an unpaid or pending order, please try again later or cancel it'));
}
$plan = Plan::findOrFail($request->input('plan_id'));
$planService = new PlanService($plan);
$planService->validatePurchase($user, $request->input('period'));
$order = OrderService::createFromRequest(
$user,
$plan,
$request->input('period'),
$request->input('coupon_code')
);
return $this->success($order->trade_no);
}
protected function applyCoupon(Order $order, string $couponCode): void
{
$couponService = new CouponService($couponCode);
if (!$couponService->use($order)) {
throw new ApiException(__('Coupon failed'));
}
$order->coupon_id = $couponService->getId();
}
protected function handleUserBalance(Order $order, User $user, UserService $userService): void
{
$remainingBalance = $user->balance - $order->total_amount;
if ($remainingBalance > 0) {
if (!$userService->addBalance($order->user_id, -$order->total_amount)) {
throw new ApiException(__('Insufficient balance'));
}
$order->balance_amount = $order->total_amount;
$order->total_amount = 0;
} else {
if (!$userService->addBalance($order->user_id, -$user->balance)) {
throw new ApiException(__('Insufficient balance'));
}
$order->balance_amount = $user->balance;
$order->total_amount = $order->total_amount - $user->balance;
}
}
public function checkout(Request $request)
{
$tradeNo = $request->input('trade_no');
$method = $request->input('method');
$order = Order::where('trade_no', $tradeNo)
->where('user_id', $request->user()->id)
->where('status', 0)
->first();
if (!$order) {
return $this->fail([400, __('Order does not exist or has been paid')]);
}
// free process
if ($order->total_amount <= 0) {
$orderService = new OrderService($order);
if (!$orderService->paid($order->trade_no))
return $this->fail([400, '支付失败']);
return response([
'type' => -1,
'data' => true
]);
}
$payment = Payment::find($method);
if (!$payment || !$payment->enable) {
return $this->fail([400, __('Payment method is not available')]);
}
$paymentService = new PaymentService($payment->payment, $payment->id);
$order->handling_amount = NULL;
if ($payment->handling_fee_fixed || $payment->handling_fee_percent) {
$order->handling_amount = (int) round(($order->total_amount * ($payment->handling_fee_percent / 100)) + $payment->handling_fee_fixed);
}
$order->payment_id = $method;
if (!$order->save())
return $this->fail([400, __('Request failed, please try again later')]);
$result = $paymentService->pay([
'trade_no' => $tradeNo,
'total_amount' => isset($order->handling_amount) ? ($order->total_amount + $order->handling_amount) : $order->total_amount,
'user_id' => $order->user_id,
'stripe_token' => $request->input('token')
]);
return response([
'type' => $result['type'],
'data' => $result['data']
]);
}
public function check(Request $request)
{
$tradeNo = $request->input('trade_no');
$order = Order::where('trade_no', $tradeNo)
->where('user_id', $request->user()->id)
->first();
if (!$order) {
return $this->fail([400, __('Order does not exist')]);
}
return $this->success($order->status);
}
public function getPaymentMethod()
{
$methods = Payment::select([
'id',
'name',
'payment',
'icon',
'handling_fee_fixed',
'handling_fee_percent'
])
->where('enable', 1)
->orderBy('sort', 'ASC')
->get();
return $this->success($methods);
}
public function cancel(Request $request)
{
if (empty($request->input('trade_no'))) {
return $this->fail([422, __('Invalid parameter')]);
}
$order = Order::where('trade_no', $request->input('trade_no'))
->where('user_id', $request->user()->id)
->first();
if (!$order) {
return $this->fail([400, __('Order does not exist')]);
}
if ($order->status !== 0) {
return $this->fail([400, __('You can only cancel pending orders')]);
}
$orderService = new OrderService($order);
if (!$orderService->cancel()) {
return $this->fail([400, __('Cancel failed')]);
}
return $this->success(true);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Http\Resources\PlanResource;
use App\Models\Plan;
use App\Models\User;
use App\Services\PlanService;
use Illuminate\Http\Request;
class PlanController extends Controller
{
protected PlanService $planService;
public function __construct(PlanService $planService)
{
$this->planService = $planService;
}
public function fetch(Request $request)
{
$user = User::find($request->user()->id);
if ($request->input('id')) {
$plan = Plan::where('id', $request->input('id'))->first();
if (!$plan) {
return $this->fail([400, __('Subscription plan does not exist')]);
}
if (!$this->planService->isPlanAvailableForUser($plan, $user)) {
return $this->fail([400, __('Subscription plan does not exist')]);
}
return $this->success(PlanResource::make($plan));
}
$plans = $this->planService->getAvailablePlans();
return $this->success(PlanResource::collection($plans));
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Http\Resources\NodeResource;
use App\Models\User;
use App\Services\ServerService;
use App\Services\UserService;
use Illuminate\Http\Request;
class ServerController extends Controller
{
public function fetch(Request $request)
{
$user = User::find($request->user()->id);
$servers = [];
$userService = new UserService();
if ($userService->isAvailable($user)) {
$servers = ServerService::getAvailableServers($user);
}
$eTag = sha1(json_encode(array_column($servers, 'cache_key')));
if (strpos($request->header('If-None-Match', ''), $eTag) !== false ) {
return response(null,304);
}
$data = NodeResource::collection($servers);
return response([
'data' => $data
])->header('ETag', "\"{$eTag}\"");
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Http\Resources\TrafficLogResource;
use App\Models\StatUser;
use App\Services\StatisticalService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class StatController extends Controller
{
public function getTrafficLog(Request $request)
{
$startDate = now()->startOfMonth()->timestamp;
$records = StatUser::query()
->where('user_id', $request->user()->id)
->where('record_at', '>=', $startDate)
->orderBy('record_at', 'DESC')
->get();
$data = TrafficLogResource::collection(collect($records));
return $this->success($data);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\TelegramService;
use Illuminate\Http\Request;
class TelegramController extends Controller
{
public function getBotInfo()
{
$telegramService = new TelegramService();
$response = $telegramService->getMe();
$data = [
'username' => $response->result->username
];
return $this->success($data);
}
public function unbind(Request $request)
{
$user = User::where('user_id', $request->user()->id)->first();
}
}

View File

@@ -0,0 +1,154 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\TicketSave;
use App\Http\Requests\User\TicketWithdraw;
use App\Http\Resources\TicketResource;
use App\Models\Ticket;
use App\Models\TicketMessage;
use App\Models\User;
use App\Services\TicketService;
use App\Utils\Dict;
use Illuminate\Http\Request;
use App\Services\Plugin\HookManager;
use Illuminate\Support\Facades\Log;
class TicketController extends Controller
{
public function fetch(Request $request)
{
if ($request->input('id')) {
$ticket = Ticket::where('id', $request->input('id'))
->where('user_id', $request->user()->id)
->first()
->load('message');
if (!$ticket) {
return $this->fail([400, __('Ticket does not exist')]);
}
$ticket['message'] = TicketMessage::where('ticket_id', $ticket->id)->get();
$ticket['message']->each(function ($message) use ($ticket) {
$message['is_me'] = ($message['user_id'] == $ticket->user_id);
});
return $this->success(TicketResource::make($ticket)->additional(['message' => true]));
}
$ticket = Ticket::where('user_id', $request->user()->id)
->orderBy('created_at', 'DESC')
->get();
return $this->success(TicketResource::collection($ticket));
}
public function save(TicketSave $request)
{
$ticketService = new TicketService();
$ticket = $ticketService->createTicket(
$request->user()->id,
$request->input('subject'),
$request->input('level'),
$request->input('message')
);
HookManager::call('ticket.create.after', $ticket);
return $this->success(true);
}
public function reply(Request $request)
{
if (empty($request->input('id'))) {
return $this->fail([400, __('Invalid parameter')]);
}
if (empty($request->input('message'))) {
return $this->fail([400, __('Message cannot be empty')]);
}
$ticket = Ticket::where('id', $request->input('id'))
->where('user_id', $request->user()->id)
->first();
if (!$ticket) {
return $this->fail([400, __('Ticket does not exist')]);
}
if ($ticket->status) {
return $this->fail([400, __('The ticket is closed and cannot be replied')]);
}
if ((int) admin_setting('ticket_must_wait_reply', 0) && $request->user()->id == $this->getLastMessage($ticket->id)->user_id) {
return $this->fail(codeResponse: [400, __('Please wait for the technical enginneer to reply')]);
}
$ticketService = new TicketService();
if (
!$ticketService->reply(
$ticket,
$request->input('message'),
$request->user()->id
)
) {
return $this->fail([400, __('Ticket reply failed')]);
}
HookManager::call('ticket.reply.user.after', $ticket);
return $this->success(true);
}
public function close(Request $request)
{
if (empty($request->input('id'))) {
return $this->fail([422, __('Invalid parameter')]);
}
$ticket = Ticket::where('id', $request->input('id'))
->where('user_id', $request->user()->id)
->first();
if (!$ticket) {
return $this->fail([400, __('Ticket does not exist')]);
}
$ticket->status = Ticket::STATUS_CLOSED;
if (!$ticket->save()) {
return $this->fail([500, __('Close failed')]);
}
return $this->success(true);
}
private function getLastMessage($ticketId)
{
return TicketMessage::where('ticket_id', $ticketId)
->orderBy('id', 'DESC')
->first();
}
public function withdraw(TicketWithdraw $request)
{
if ((int) admin_setting('withdraw_close_enable', 0)) {
return $this->fail([400, 'Unsupported withdraw']);
}
if (
!in_array(
$request->input('withdraw_method'),
admin_setting('commission_withdraw_method', Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT)
)
) {
return $this->fail([422, __('Unsupported withdrawal method')]);
}
$user = User::find($request->user()->id);
$limit = admin_setting('commission_withdraw_limit', 100);
if ($limit > ($user->commission_balance / 100)) {
return $this->fail([422, __('The current required minimum withdrawal commission is :limit', ['limit' => $limit])]);
}
try {
$ticketService = new TicketService();
$subject = __('[Commission Withdrawal Request] This ticket is opened by the system');
$message = sprintf(
"%s\r\n%s",
__('Withdrawal method') . "" . $request->input('withdraw_method'),
__('Withdrawal account') . "" . $request->input('withdraw_account')
);
$ticket = $ticketService->createTicket(
$request->user()->id,
$subject,
2,
$message
);
} catch (\Exception $e) {
throw $e;
}
HookManager::call('ticket.create.after', $ticket);
return $this->success(true);
}
}

View File

@@ -0,0 +1,223 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\UserChangePassword;
use App\Http\Requests\User\UserTransfer;
use App\Http\Requests\User\UserUpdate;
use App\Models\Order;
use App\Models\Plan;
use App\Models\Ticket;
use App\Models\User;
use App\Services\Auth\LoginService;
use App\Services\AuthService;
use App\Services\Plugin\HookManager;
use App\Services\UserService;
use App\Utils\CacheKey;
use App\Utils\Helper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class UserController extends Controller
{
protected $loginService;
public function __construct(
LoginService $loginService
) {
$this->loginService = $loginService;
}
public function getActiveSession(Request $request)
{
$user = $request->user();
$authService = new AuthService($user);
return $this->success($authService->getSessions());
}
public function removeActiveSession(Request $request)
{
$user = $request->user();
$authService = new AuthService($user);
return $this->success($authService->removeSession($request->input('session_id')));
}
public function checkLogin(Request $request)
{
$data = [
'is_login' => $request->user()?->id ? true : false
];
if ($request->user()?->is_admin) {
$data['is_admin'] = true;
}
return $this->success($data);
}
public function changePassword(UserChangePassword $request)
{
$user = $request->user();
if (
!Helper::multiPasswordVerify(
$user->password_algo,
$user->password_salt,
$request->input('old_password'),
$user->password
)
) {
return $this->fail([400, __('The old password is wrong')]);
}
$user->password = password_hash($request->input('new_password'), PASSWORD_DEFAULT);
$user->password_algo = NULL;
$user->password_salt = NULL;
if (!$user->save()) {
return $this->fail([400, __('Save failed')]);
}
$currentToken = $user->currentAccessToken();
if ($currentToken) {
$user->tokens()->where('id', '!=', $currentToken->id)->delete();
} else {
$user->tokens()->delete();
}
return $this->success(true);
}
public function info(Request $request)
{
$user = User::where('id', $request->user()->id)
->select([
'email',
'transfer_enable',
'last_login_at',
'created_at',
'banned',
'remind_expire',
'remind_traffic',
'expired_at',
'balance',
'commission_balance',
'plan_id',
'discount',
'commission_rate',
'telegram_id',
'uuid'
])
->first();
if (!$user) {
return $this->fail([400, __('The user does not exist')]);
}
$user['avatar_url'] = 'https://cdn.v2ex.com/gravatar/' . md5($user->email) . '?s=64&d=identicon';
return $this->success($user);
}
public function getStat(Request $request)
{
$stat = [
Order::where('status', 0)
->where('user_id', $request->user()->id)
->count(),
Ticket::where('status', 0)
->where('user_id', $request->user()->id)
->count(),
User::where('invite_user_id', $request->user()->id)
->count()
];
return $this->success($stat);
}
public function getSubscribe(Request $request)
{
$user = User::where('id', $request->user()->id)
->select([
'plan_id',
'token',
'expired_at',
'u',
'd',
'transfer_enable',
'email',
'uuid',
'device_limit',
'speed_limit',
'next_reset_at'
])
->first();
if (!$user) {
return $this->fail([400, __('The user does not exist')]);
}
if ($user->plan_id) {
$user['plan'] = Plan::find($user->plan_id);
if (!$user['plan']) {
return $this->fail([400, __('Subscription plan does not exist')]);
}
}
$user['subscribe_url'] = Helper::getSubscribeUrl($user['token']);
$userService = new UserService();
$user['reset_day'] = $userService->getResetDay($user);
$user = HookManager::filter('user.subscribe.response', $user);
return $this->success($user);
}
public function resetSecurity(Request $request)
{
$user = $request->user();
$user->uuid = Helper::guid(true);
$user->token = Helper::guid();
if (!$user->save()) {
return $this->fail([400, __('Reset failed')]);
}
return $this->success(Helper::getSubscribeUrl($user->token));
}
public function update(UserUpdate $request)
{
$updateData = $request->only([
'remind_expire',
'remind_traffic'
]);
$user = $request->user();
try {
$user->update($updateData);
} catch (\Exception $e) {
return $this->fail([400, __('Save failed')]);
}
return $this->success(true);
}
public function transfer(UserTransfer $request)
{
$amount = $request->input('transfer_amount');
try {
DB::transaction(function () use ($request, $amount) {
$user = User::lockForUpdate()->find($request->user()->id);
if (!$user) {
throw new \Exception(__('The user does not exist'));
}
if ($amount > $user->commission_balance) {
throw new \Exception(__('Insufficient commission balance'));
}
$user->commission_balance -= $amount;
$user->balance += $amount;
if (!$user->save()) {
throw new \Exception(__('Transfer failed'));
}
});
} catch (\Exception $e) {
return $this->fail([400, $e->getMessage()]);
}
return $this->success(true);
}
public function getQuickLoginUrl(Request $request)
{
$user = $request->user();
$url = $this->loginService->generateQuickLoginUrl($user, $request->input('redirect'));
return $this->success($url);
}
}