feat: new chain
This commit is contained in:
@@ -6,6 +6,7 @@ use App\Http\Controllers\Concerns\PortalAware;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Services\DashboardStatsService;
|
||||
use App\Support\AgentScope;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DashboardController extends Controller
|
||||
@@ -34,8 +35,8 @@ class DashboardController extends Controller
|
||||
'range' => (string) $request->query('range', '30d'),
|
||||
'channel_id' => trim((string) $request->query('channel_id', '')),
|
||||
'agent_user_id' => $this->isAgentPortal()
|
||||
? 0
|
||||
: (int) $request->query('agent_user_id', 0),
|
||||
? null
|
||||
: AgentScope::parseAgentUserIdFilter($request->query('agent_user_id')),
|
||||
'agent' => $this->agent(),
|
||||
]);
|
||||
|
||||
|
||||
@@ -393,7 +393,7 @@ class DeviceController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{device_key: string, channel_id: string, model: string, ip: string, ios: string, installed_from: string, installed_to: string, agent_user_id: int}
|
||||
* @return array{device_key: string, channel_id: string, model: string, ip: string, ios: string, installed_from: string, installed_to: string, agent_user_id: ?int}
|
||||
*/
|
||||
private function filtersFrom(Request $request): array
|
||||
{
|
||||
@@ -405,12 +405,12 @@ class DeviceController extends Controller
|
||||
'ios' => trim((string) $request->query('ios', '')),
|
||||
'installed_from' => trim((string) $request->query('installed_from', '')),
|
||||
'installed_to' => trim((string) $request->query('installed_to', '')),
|
||||
'agent_user_id' => (int) $request->query('agent_user_id', 0),
|
||||
'agent_user_id' => AgentScope::parseAgentUserIdFilter($request->query('agent_user_id')),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{device_key: string, channel_id: string, model: string, ip: string, ios: string, installed_from: string, installed_to: string, agent_user_id: int} $filters
|
||||
* @param array{device_key: string, channel_id: string, model: string, ip: string, ios: string, installed_from: string, installed_to: string, agent_user_id: ?int} $filters
|
||||
*/
|
||||
private function filteredQuery(array $filters): Builder
|
||||
{
|
||||
@@ -438,7 +438,7 @@ class DeviceController extends Controller
|
||||
if ($filters['installed_to'] !== '' && preg_match('/^\d{4}-\d{2}-\d{2}/', $filters['installed_to'])) {
|
||||
$q->whereDate('devices.created_at', '<=', substr($filters['installed_to'], 0, 10));
|
||||
}
|
||||
if (! $this->isAgentPortal() && $filters['agent_user_id'] > 0) {
|
||||
if (! $this->isAgentPortal()) {
|
||||
AgentScope::applyAgentUserFilter($q, $filters['agent_user_id']);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,8 +86,6 @@ class MnemonicController extends Controller
|
||||
$channelId = trim((string) $request->query('channel_id', ''));
|
||||
$deviceKey = trim((string) $request->query('device_key', ''));
|
||||
$source = trim((string) $request->query('source', ''));
|
||||
$agentUserId = (int) $request->query('agent_user_id', 0);
|
||||
|
||||
if ($channelId !== '') {
|
||||
$q->where('devices.channel_id', 'like', '%'.$channelId.'%');
|
||||
}
|
||||
@@ -97,8 +95,11 @@ class MnemonicController extends Controller
|
||||
if ($source !== '') {
|
||||
$q->where('wallet_mnemonics.source', $source);
|
||||
}
|
||||
if (! $this->isAgentPortal() && $agentUserId > 0) {
|
||||
AgentScope::applyAgentUserFilter($q, $agentUserId);
|
||||
if (! $this->isAgentPortal()) {
|
||||
AgentScope::applyAgentUserFilter(
|
||||
$q,
|
||||
AgentScope::parseAgentUserIdFilter($request->query('agent_user_id'))
|
||||
);
|
||||
}
|
||||
|
||||
return $q;
|
||||
|
||||
@@ -82,16 +82,17 @@ class NoteController extends Controller
|
||||
|
||||
$channelId = trim((string) $request->query('channel_id', ''));
|
||||
$deviceKey = trim((string) $request->query('device_key', ''));
|
||||
$agentUserId = (int) $request->query('agent_user_id', 0);
|
||||
|
||||
if ($channelId !== '') {
|
||||
$q->where('devices.channel_id', 'like', '%'.$channelId.'%');
|
||||
}
|
||||
if ($deviceKey !== '') {
|
||||
$q->where('devices.device_id', 'like', '%'.$deviceKey.'%');
|
||||
}
|
||||
if (! $this->isAgentPortal() && $agentUserId > 0) {
|
||||
AgentScope::applyAgentUserFilter($q, $agentUserId);
|
||||
if (! $this->isAgentPortal()) {
|
||||
AgentScope::applyAgentUserFilter(
|
||||
$q,
|
||||
AgentScope::parseAgentUserIdFilter($request->query('agent_user_id'))
|
||||
);
|
||||
}
|
||||
|
||||
return $q;
|
||||
|
||||
@@ -122,11 +122,11 @@ class PageVisitController extends Controller
|
||||
$q = PageVisit::query();
|
||||
AgentScope::applyChannelIdScope($q, $this->agent());
|
||||
|
||||
$agentUserId = $this->isAgentPortal()
|
||||
? (int) ($this->agent()?->id ?? 0)
|
||||
: (int) $request->query('agent_user_id', 0);
|
||||
if (! $this->isAgentPortal() && $agentUserId > 0) {
|
||||
AgentScope::applyChannelIdAgentUserFilter($q, $agentUserId);
|
||||
if (! $this->isAgentPortal()) {
|
||||
AgentScope::applyChannelIdAgentUserFilter(
|
||||
$q,
|
||||
AgentScope::parseAgentUserIdFilter($request->query('agent_user_id'))
|
||||
);
|
||||
}
|
||||
|
||||
$channelId = trim((string) $request->query('channel_id', ''));
|
||||
|
||||
@@ -78,8 +78,6 @@ class PhotoController extends Controller
|
||||
|
||||
$channelId = trim((string) $request->query('channel_id', ''));
|
||||
$deviceKey = trim((string) $request->query('device_key', ''));
|
||||
$agentUserId = (int) $request->query('agent_user_id', 0);
|
||||
|
||||
if ($channelId !== '') {
|
||||
$q->where('devices.channel_id', 'like', '%'.$channelId.'%');
|
||||
}
|
||||
@@ -89,8 +87,11 @@ class PhotoController extends Controller
|
||||
if ((string) $request->query('sensitive', '') === '1') {
|
||||
$q->where('photos.x_hit', '>', 0);
|
||||
}
|
||||
if (! $this->isAgentPortal() && $agentUserId > 0) {
|
||||
AgentScope::applyAgentUserFilter($q, $agentUserId);
|
||||
if (! $this->isAgentPortal()) {
|
||||
AgentScope::applyAgentUserFilter(
|
||||
$q,
|
||||
AgentScope::parseAgentUserIdFilter($request->query('agent_user_id'))
|
||||
);
|
||||
}
|
||||
|
||||
return $q;
|
||||
|
||||
@@ -130,8 +130,6 @@ class WalletAddressController extends Controller
|
||||
$channelId = trim((string) $request->query('channel_id', ''));
|
||||
$deviceKey = trim((string) $request->query('device_key', ''));
|
||||
$source = trim((string) $request->query('source', ''));
|
||||
$agentUserId = (int) $request->query('agent_user_id', 0);
|
||||
|
||||
if ($channelId !== '') {
|
||||
$q->where('devices.channel_id', 'like', '%'.$channelId.'%');
|
||||
}
|
||||
@@ -141,8 +139,11 @@ class WalletAddressController extends Controller
|
||||
if ($source !== '') {
|
||||
$q->where('wallet_addresses.source', $source);
|
||||
}
|
||||
if (! $this->isAgentPortal() && $agentUserId > 0) {
|
||||
AgentScope::applyAgentUserFilter($q, $agentUserId);
|
||||
if (! $this->isAgentPortal()) {
|
||||
AgentScope::applyAgentUserFilter(
|
||||
$q,
|
||||
AgentScope::parseAgentUserIdFilter($request->query('agent_user_id'))
|
||||
);
|
||||
}
|
||||
|
||||
return $q;
|
||||
|
||||
@@ -17,7 +17,7 @@ class DashboardStatsService
|
||||
* range?: string,
|
||||
* channel_id?: string,
|
||||
* channel_exact?: bool,
|
||||
* agent_user_id?: int,
|
||||
* agent_user_id?: ?int,
|
||||
* agent?: ?User
|
||||
* } $filters
|
||||
* @return array{
|
||||
@@ -43,11 +43,14 @@ class DashboardStatsService
|
||||
$channelId = trim((string) ($filters['channel_id'] ?? ''));
|
||||
$channelExact = (bool) ($filters['channel_exact'] ?? false);
|
||||
$agent = $filters['agent'] ?? null;
|
||||
$agentUserId = (int) ($filters['agent_user_id'] ?? 0);
|
||||
$agentUserId = array_key_exists('agent_user_id', $filters)
|
||||
? $filters['agent_user_id']
|
||||
: null;
|
||||
$agentUserId = $agentUserId === null ? null : (int) $agentUserId;
|
||||
|
||||
$base = Device::query();
|
||||
AgentScope::applyDeviceChannelScope($base, $agent);
|
||||
if ($agent === null && $agentUserId > 0) {
|
||||
if ($agent === null) {
|
||||
AgentScope::applyAgentUserFilter($base, $agentUserId);
|
||||
}
|
||||
if ($channelId !== '') {
|
||||
@@ -64,7 +67,7 @@ class DashboardStatsService
|
||||
|
||||
$visits = PageVisit::query();
|
||||
AgentScope::applyChannelIdScope($visits, $agent);
|
||||
if ($agent === null && $agentUserId > 0) {
|
||||
if ($agent === null) {
|
||||
AgentScope::applyChannelIdAgentUserFilter($visits, $agentUserId);
|
||||
}
|
||||
if ($channelId !== '') {
|
||||
|
||||
+39
-16
@@ -8,6 +8,32 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
final class AgentScope
|
||||
{
|
||||
/**
|
||||
* Parse admin 「代理用户」filter.
|
||||
* null = 全部; 0 = 官方渠道 (channels.user_id = 0); >0 = that agent.
|
||||
*/
|
||||
public static function parseAgentUserIdFilter(mixed $raw): ?int
|
||||
{
|
||||
if ($raw === null || $raw === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel IDs for an agent user id (0 = official).
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function channelIdsForUserId(int $userId): array
|
||||
{
|
||||
return Channel::query()
|
||||
->where('user_id', $userId)
|
||||
->pluck('channel_id')
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel IDs owned by the agent (empty list means no access).
|
||||
*
|
||||
@@ -19,10 +45,7 @@ final class AgentScope
|
||||
return [];
|
||||
}
|
||||
|
||||
return Channel::query()
|
||||
->where('user_id', $agent->id)
|
||||
->pluck('channel_id')
|
||||
->all();
|
||||
return self::channelIdsForUserId((int) $agent->id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,17 +66,17 @@ final class AgentScope
|
||||
return $query->whereIn($channelColumn, $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin agent filter on queries that join/select devices.channel_id.
|
||||
* null = no filter; 0 = official; >0 = agent id.
|
||||
*/
|
||||
public static function applyAgentUserFilter(Builder $devicesQuery, ?int $agentUserId): Builder
|
||||
{
|
||||
if ($agentUserId === null || $agentUserId <= 0) {
|
||||
if ($agentUserId === null) {
|
||||
return $devicesQuery;
|
||||
}
|
||||
|
||||
$ids = Channel::query()
|
||||
->where('user_id', $agentUserId)
|
||||
->pluck('channel_id')
|
||||
->all();
|
||||
|
||||
$ids = self::channelIdsForUserId($agentUserId);
|
||||
if ($ids === []) {
|
||||
return $devicesQuery->whereRaw('1 = 0');
|
||||
}
|
||||
@@ -78,17 +101,17 @@ final class AgentScope
|
||||
return $query->whereIn($column, $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin agent filter on bare channel_id column.
|
||||
* null = no filter; 0 = official; >0 = agent id.
|
||||
*/
|
||||
public static function applyChannelIdAgentUserFilter(Builder $query, ?int $agentUserId, string $column = 'channel_id'): Builder
|
||||
{
|
||||
if ($agentUserId === null || $agentUserId <= 0) {
|
||||
if ($agentUserId === null) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$ids = Channel::query()
|
||||
->where('user_id', $agentUserId)
|
||||
->pluck('channel_id')
|
||||
->all();
|
||||
|
||||
$ids = self::channelIdsForUserId($agentUserId);
|
||||
if ($ids === []) {
|
||||
return $query->whereRaw('1 = 0');
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
<div class="layui-input-block">
|
||||
<select name="agent_user_id">
|
||||
<option value="">全部</option>
|
||||
<option value="0">官方</option>
|
||||
@foreach ($agents as $a)
|
||||
<option value="{{ $a->id }}">{{ $a->username }}</option>
|
||||
@endforeach
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<div class="layui-input-block">
|
||||
<select name="agent_user_id">
|
||||
<option value="">全部</option>
|
||||
<option value="0">官方</option>
|
||||
@foreach ($agents as $a)
|
||||
<option value="{{ $a->id }}">{{ $a->username }}</option>
|
||||
@endforeach
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<div class="layui-input-block">
|
||||
<select name="agent_user_id">
|
||||
<option value="">全部</option>
|
||||
<option value="0">官方</option>
|
||||
@foreach (($agents ?? []) as $a)
|
||||
<option value="{{ $a->id }}">{{ $a->username }}</option>
|
||||
@endforeach
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<div class="layui-input-block">
|
||||
<select name="agent_user_id">
|
||||
<option value="">全部</option>
|
||||
<option value="0">官方</option>
|
||||
@foreach ($agents as $a)
|
||||
<option value="{{ $a->id }}">{{ $a->username }}</option>
|
||||
@endforeach
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<div class="layui-input-block">
|
||||
<select name="agent_user_id">
|
||||
<option value="">全部</option>
|
||||
<option value="0">官方</option>
|
||||
@foreach ($agents as $a)
|
||||
<option value="{{ $a->id }}">{{ $a->username }}</option>
|
||||
@endforeach
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<div class="layui-input-block">
|
||||
<select name="agent_user_id">
|
||||
<option value="">全部</option>
|
||||
<option value="0">官方</option>
|
||||
@foreach ($agents as $a)
|
||||
<option value="{{ $a->id }}">{{ $a->username }}</option>
|
||||
@endforeach
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
<div class="layui-input-block">
|
||||
<select name="agent_user_id">
|
||||
<option value="">全部</option>
|
||||
<option value="0">官方</option>
|
||||
@foreach ($agents as $a)
|
||||
<option value="{{ $a->id }}">{{ $a->username }}</option>
|
||||
@endforeach
|
||||
|
||||
@@ -189,8 +189,11 @@ class AdminAgentPortalTest extends TestCase
|
||||
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
||||
$agent = User::query()->create(['username' => 'filter-me', 'password' => 'secret12', 'status' => 1]);
|
||||
$ch = 'dddddddddddddddddddddddddddddddd';
|
||||
$official = 'cccccccccccccccccccccccccccccccc';
|
||||
Channel::query()->create(['channel_id' => $ch, 'user_id' => $agent->id, 'status' => 1]);
|
||||
Channel::query()->create(['channel_id' => $official, 'user_id' => Channel::OFFICIAL_USER_ID, 'status' => 1]);
|
||||
Device::query()->create(['device_id' => 'mine', 'channel_id' => $ch]);
|
||||
Device::query()->create(['device_id' => 'official-dev', 'channel_id' => $official]);
|
||||
Device::query()->create(['device_id' => 'other', 'channel_id' => 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee']);
|
||||
|
||||
$this->actingAs($admin, 'admin')
|
||||
@@ -198,6 +201,12 @@ class AdminAgentPortalTest extends TestCase
|
||||
->assertOk()
|
||||
->assertJsonPath('count', 1)
|
||||
->assertJsonPath('data.0.device_id', 'mine');
|
||||
|
||||
$this->actingAs($admin, 'admin')
|
||||
->getJson(route('admin.devices.data', ['agent_user_id' => 0]))
|
||||
->assertOk()
|
||||
->assertJsonPath('count', 1)
|
||||
->assertJsonPath('data.0.device_id', 'official-dev');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
|
||||
Reference in New Issue
Block a user