updated apis #5

Merged
soheilkhaledabadi merged 1 commits from new-api into clean-main 2026-07-10 20:56:20 +00:00
2 changed files with 102 additions and 21 deletions

View File

@@ -6,6 +6,8 @@ use App\Http\Controllers\Controller;
use App\Models\BylawDocument; use App\Models\BylawDocument;
use App\Models\BylawLegalAuthority; use App\Models\BylawLegalAuthority;
use App\Models\BylawSubjectCategory; use App\Models\BylawSubjectCategory;
use App\Models\BylawNode;
use App\Models\BylawCodificationLink;
use App\Traits\BaseApiResponse; use App\Traits\BaseApiResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -49,7 +51,7 @@ class BylawApiController extends Controller
'per_page' => 'nullable|integer|min:1|max:100', 'per_page' => 'nullable|integer|min:1|max:100',
]); ]);
$query = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) $query = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority', 'nodes'])
->where('is_published', true); ->where('is_published', true);
if ($request->filled('category_id')) { if ($request->filled('category_id')) {
@@ -92,6 +94,63 @@ class BylawApiController extends Controller
return $this->success($responseData, 'Success', 'Bylaws Fetched'); return $this->success($responseData, 'Success', 'Bylaws Fetched');
} }
public function single($id)
{
$bylaw = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority', 'nodes'])
->where('is_published', true)
->find($id);
if (!$bylaw) {
return $this->failed([], [
'title' => 'خطا',
'message' => 'آیین‌نامه مورد نظر یافت نشد.'
], 404);
}
$formatted = $this->formatBylaw($bylaw);
if (!$formatted['is_locked']) {
$nodes = BylawNode::with('type')
->where('document_id', $bylaw->id)
->orderBy('sort_order', 'asc')
->get()
->map(function ($node) {
return [
'id' => $node->id,
'parent_id' => $node->parent_id,
'node_type' => optional($node->type)->name,
'node_identifier' => $node->node_identifier,
'title' => $node->title,
'node_content' => $node->node_content,
'sort_order' => $node->sort_order,
'valid_from' => $node->valid_from,
'valid_to' => $node->valid_to,
'node_status' => $node->node_status,
];
});
$formatted['nodes'] = $nodes->toArray();
$links = BylawCodificationLink::where('source_entity_type', 'bylaw_doc')
->where('source_entity_id', $bylaw->id)
->get()
->map(function ($link) {
return [
'id' => $link->id,
'target_entity_type' => $link->target_entity_type,
'target_entity_id' => $link->target_entity_id,
'link_type' => $link->link_type,
'effective_date' => $link->effective_date,
'justification' => $link->justification,
];
});
$formatted['links'] = $links->toArray();
}
return $this->success($formatted, 'Success', 'Bylaw Document Details Fetched');
}
public function search(Request $request) public function search(Request $request)
{ {
$request->validate([ $request->validate([
@@ -101,7 +160,7 @@ class BylawApiController extends Controller
'per_page' => 'nullable|integer|min:1|max:100', 'per_page' => 'nullable|integer|min:1|max:100',
]); ]);
$query = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) $query = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority', 'nodes'])
->where('is_published', true); ->where('is_published', true);
if ($request->filled('search')) { if ($request->filled('search')) {
@@ -111,7 +170,10 @@ class BylawApiController extends Controller
$q->where('title', 'LIKE', "%{$searchTerm}%") $q->where('title', 'LIKE', "%{$searchTerm}%")
->orWhere('description', 'LIKE', "%{$searchTerm}%") ->orWhere('description', 'LIKE', "%{$searchTerm}%")
->orWhere('promulgation_number', 'LIKE', "%{$searchTerm}%") ->orWhere('promulgation_number', 'LIKE', "%{$searchTerm}%")
->orWhere('official_gazette_number', 'LIKE', "%{$searchTerm}%"); ->orWhere('official_gazette_number', 'LIKE', "%{$searchTerm}%")
->orWhereHas('nodes', function ($nodeQuery) use ($searchTerm) {
$nodeQuery->where('node_content', 'LIKE', "%{$searchTerm}%");
});
}); });
} }
@@ -147,7 +209,7 @@ class BylawApiController extends Controller
{ {
$searchTerm = $request->input('search'); $searchTerm = $request->input('search');
$query = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) $query = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority', 'nodes'])
->where('is_published', true); ->where('is_published', true);
if (!empty($searchTerm)) { if (!empty($searchTerm)) {
@@ -155,7 +217,10 @@ class BylawApiController extends Controller
$q->where('title', 'LIKE', "%{$searchTerm}%") $q->where('title', 'LIKE', "%{$searchTerm}%")
->orWhere('description', 'LIKE', "%{$searchTerm}%") ->orWhere('description', 'LIKE', "%{$searchTerm}%")
->orWhere('promulgation_number', 'LIKE', "%{$searchTerm}%") ->orWhere('promulgation_number', 'LIKE', "%{$searchTerm}%")
->orWhere('official_gazette_number', 'LIKE', "%{$searchTerm}%"); ->orWhere('official_gazette_number', 'LIKE', "%{$searchTerm}%")
->orWhereHas('nodes', function ($nodeQuery) use ($searchTerm) {
$nodeQuery->where('node_content', 'LIKE', "%{$searchTerm}%");
});
}); });
} }
@@ -172,7 +237,7 @@ class BylawApiController extends Controller
public function pager($id) public function pager($id)
{ {
$current = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) $current = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority', 'nodes'])
->where('is_published', true) ->where('is_published', true)
->find($id); ->find($id);
@@ -183,7 +248,7 @@ class BylawApiController extends Controller
], 404); ], 404);
} }
$previousItem = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) $previousItem = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority', 'nodes'])
->where('is_published', true) ->where('is_published', true)
->where(function ($query) use ($current) { ->where(function ($query) use ($current) {
if ($current->approval_date) { if ($current->approval_date) {
@@ -200,7 +265,7 @@ class BylawApiController extends Controller
->orderBy('id', 'desc') ->orderBy('id', 'desc')
->first(); ->first();
$nextItem = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) $nextItem = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority', 'nodes'])
->where('is_published', true) ->where('is_published', true)
->where(function ($query) use ($current) { ->where(function ($query) use ($current) {
if ($current->approval_date) { if ($current->approval_date) {
@@ -238,13 +303,21 @@ class BylawApiController extends Controller
{ {
$isLocked = $this->isLockedForCurrentUser($item); $isLocked = $this->isLockedForCurrentUser($item);
// Map description: If description is null, combine all node_content into one full text!
$description = $item->description;
if (empty($description) && $item->relationLoaded('nodes')) {
$description = $item->nodes->pluck('node_content')->filter()->implode("\n\n");
}
return [ return [
'id' => $item->id, 'id' => $item->id,
'title' => $item->title, 'title' => $item->title,
'category_id' => $item->category_id, 'category_id' => $item->category_id,
'category_name' => optional($item->category)->name, 'category_name' => optional($item->category)->name,
'approval_authority_id' => $item->approval_authority_id,
'approval_authority_name' => optional($item->approvalAuthority)->name, 'approval_authority_name' => optional($item->approvalAuthority)->name,
'approval_date' => $item->approval_date, 'approval_date' => $item->approval_date,
'promulgation_authority_id' => $item->promulgation_authority_id,
'promulgation_authority_name'=> optional($item->promulgationAuthority)->name, 'promulgation_authority_name'=> optional($item->promulgationAuthority)->name,
'promulgation_date' => $item->promulgation_date, 'promulgation_date' => $item->promulgation_date,
'promulgation_number' => $item->promulgation_number, 'promulgation_number' => $item->promulgation_number,
@@ -254,7 +327,7 @@ class BylawApiController extends Controller
'bylaw_type' => $item->bylaw_type, 'bylaw_type' => $item->bylaw_type,
'global_status' => $item->global_status, 'global_status' => $item->global_status,
'pdf_file_url' => $isLocked ? null : ($item->pdf_file_path ? asset('storage/' . $item->pdf_file_path) : null), 'pdf_file_url' => $isLocked ? null : ($item->pdf_file_path ? asset('storage/' . $item->pdf_file_path) : null),
'description' => $isLocked ? null : $item->description, 'description' => $isLocked ? null : $description,
'is_locked' => $isLocked, 'is_locked' => $isLocked,
]; ];
} }

View File

@@ -46,14 +46,17 @@ class GlobalSearchApiController extends Controller
if (!empty($searchTerm)) { if (!empty($searchTerm)) {
$limitPerTable = 100; // Performance safeguard $limitPerTable = 100; // Performance safeguard
// 1. Search Bylaw Documents // 1. Search Bylaw Documents (Including deeper node_content search)
$bylaws = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) $bylaws = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority', 'nodes'])
->where('is_published', true) ->where('is_published', true)
->where(function ($q) use ($searchTerm) { ->where(function ($q) use ($searchTerm) {
$q->where('title', 'LIKE', "%{$searchTerm}%") $q->where('title', 'LIKE', "%{$searchTerm}%")
->orWhere('description', 'LIKE', "%{$searchTerm}%") ->orWhere('description', 'LIKE', "%{$searchTerm}%")
->orWhere('promulgation_number', 'LIKE', "%{$searchTerm}%") ->orWhere('promulgation_number', 'LIKE', "%{$searchTerm}%")
->orWhere('official_gazette_number', 'LIKE', "%{$searchTerm}%"); ->orWhere('official_gazette_number', 'LIKE', "%{$searchTerm}%")
->orWhereHas('nodes', function ($nodeQuery) use ($searchTerm) {
$nodeQuery->where('node_content', 'LIKE', "%{$searchTerm}%");
});
})->take($limitPerTable)->get()->map(function($item) { })->take($limitPerTable)->get()->map(function($item) {
$formatted = $this->formatBylaw($item); $formatted = $this->formatBylaw($item);
$formatted['type'] = 'bylaw'; $formatted['type'] = 'bylaw';
@@ -130,11 +133,9 @@ class GlobalSearchApiController extends Controller
if ($sortBy === 'number') { if ($sortBy === 'number') {
$sortCallback = function ($item) { $sortCallback = function ($item) {
// Extract whatever number property this specific item uses
return (string) ($item['promulgation_number'] ?? $item['ruling_number'] ?? $item['opinion_number'] ?? $item['number'] ?? ''); return (string) ($item['promulgation_number'] ?? $item['ruling_number'] ?? $item['opinion_number'] ?? $item['number'] ?? '');
}; };
// Use Natural Sorting so "2" comes before "10"
if ($sortOrder === 'asc') { if ($sortOrder === 'asc') {
$results = $results->sortBy($sortCallback, SORT_NATURAL)->values(); $results = $results->sortBy($sortCallback, SORT_NATURAL)->values();
} else { } else {
@@ -142,7 +143,6 @@ class GlobalSearchApiController extends Controller
} }
} elseif ($sortBy === 'date') { } elseif ($sortBy === 'date') {
$sortCallback = function ($item) { $sortCallback = function ($item) {
// Extract whatever date property this specific item uses (Note: Structural models will default to '' and group together)
return (string) ($item['approval_date'] ?? $item['ruling_date'] ?? $item['opinion_date'] ?? ''); return (string) ($item['approval_date'] ?? $item['ruling_date'] ?? $item['opinion_date'] ?? '');
}; };
@@ -186,19 +186,21 @@ class GlobalSearchApiController extends Controller
$results = collect(); $results = collect();
$totalCount = 0; $totalCount = 0;
// Take a max of 3 from each table so the quick-search array doesn't become too heavy
$limitPerTable = 3; $limitPerTable = 3;
if (!empty($searchTerm)) { if (!empty($searchTerm)) {
// 1. Bylaw Documents // 1. Bylaw Documents
$bylawQuery = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) $bylawQuery = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority', 'nodes'])
->where('is_published', true) ->where('is_published', true)
->where(function ($q) use ($searchTerm) { ->where(function ($q) use ($searchTerm) {
$q->where('title', 'LIKE', "%{$searchTerm}%") $q->where('title', 'LIKE', "%{$searchTerm}%")
->orWhere('description', 'LIKE', "%{$searchTerm}%") ->orWhere('description', 'LIKE', "%{$searchTerm}%")
->orWhere('promulgation_number', 'LIKE', "%{$searchTerm}%") ->orWhere('promulgation_number', 'LIKE', "%{$searchTerm}%")
->orWhere('official_gazette_number', 'LIKE', "%{$searchTerm}%"); ->orWhere('official_gazette_number', 'LIKE', "%{$searchTerm}%")
->orWhereHas('nodes', function ($nodeQuery) use ($searchTerm) {
$nodeQuery->where('node_content', 'LIKE', "%{$searchTerm}%");
});
}); });
$totalCount += $bylawQuery->count(); $totalCount += $bylawQuery->count();
$bylaws = $bylawQuery->take($limitPerTable)->get()->map(function($item) { $bylaws = $bylawQuery->take($limitPerTable)->get()->map(function($item) {
@@ -297,6 +299,12 @@ class GlobalSearchApiController extends Controller
{ {
$isLocked = $this->isLockedForCurrentUserGeneric($item); $isLocked = $this->isLockedForCurrentUserGeneric($item);
// Fallback description to node_content if empty
$description = $item->description;
if (empty($description) && $item->relationLoaded('nodes')) {
$description = $item->nodes->pluck('node_content')->filter()->implode("\n\n");
}
return [ return [
'id' => $item->id, 'id' => $item->id,
'title' => $item->title, 'title' => $item->title,
@@ -304,7 +312,7 @@ class GlobalSearchApiController extends Controller
'approval_authority_name' => optional($item->approvalAuthority)->name, 'approval_authority_name' => optional($item->approvalAuthority)->name,
'approval_date' => $item->approval_date, 'approval_date' => $item->approval_date,
'promulgation_number' => $item->promulgation_number, 'promulgation_number' => $item->promulgation_number,
'description' => $isLocked ? null : $item->description, 'description' => $isLocked ? null : $description,
'is_locked' => $isLocked, 'is_locked' => $isLocked,
]; ];
} }
@@ -357,7 +365,7 @@ class GlobalSearchApiController extends Controller
return [ return [
'id' => $item->id, 'id' => $item->id,
'title' => $item->title, 'title' => $item->title,
'number' => $item->number, // Added for sorting capability 'number' => $item->number,
'text' => $isLocked ? null : $context, 'text' => $isLocked ? null : $context,
'is_locked' => $isLocked, 'is_locked' => $isLocked,
'route' => array_values($this->route($modelClass, $item)), 'route' => array_values($this->route($modelClass, $item)),
@@ -378,7 +386,7 @@ class GlobalSearchApiController extends Controller
$payload = [ $payload = [
'id' => $item->id, 'id' => $item->id,
'title' => $item->title, 'title' => $item->title,
'number' => $item->number, // Available for sorting capability 'number' => $item->number,
'is_locked' => $isLocked, 'is_locked' => $isLocked,
'route' => array_values($this->route($modelClass, $item)), 'route' => array_values($this->route($modelClass, $item)),
'law' => $law?->title ?? '', 'law' => $law?->title ?? '',