search functionality
This commit is contained in:
parent
a0426842a6
commit
7925015da1
|
|
@ -21,131 +21,41 @@ use Exception;
|
|||
|
||||
class PersonController extends Controller
|
||||
{
|
||||
/**
|
||||
* Public Search API - Search for persons without authentication.
|
||||
* Allows filtering by multiple criteria.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function publicSearch(Request $request): JsonResponse
|
||||
|
||||
// Public search functionality has been moved to PublicSearchController
|
||||
|
||||
public function getRecord($id): JsonResponse
|
||||
{
|
||||
try {
|
||||
// Start with the base query
|
||||
$query = Person::query();
|
||||
|
||||
// Apply filters for Person fields
|
||||
if ($request->has('firstName') && $request->firstName !== 'all') {
|
||||
$query->where('christian_name', 'LIKE', "%{$request->firstName}%");
|
||||
}
|
||||
|
||||
if ($request->has('lastName') && $request->lastName !== 'all') {
|
||||
$query->where('surname', 'LIKE', "%{$request->lastName}%");
|
||||
}
|
||||
|
||||
// Filter by region of origin (place_of_birth in Person table)
|
||||
if ($request->has('regionOfOrigin') && $request->regionOfOrigin !== 'all') {
|
||||
$query->where('place_of_birth', 'LIKE', "%{$request->regionOfOrigin}%");
|
||||
}
|
||||
|
||||
// For filters that need to access related tables, use whereHas
|
||||
|
||||
// Filter by Year of Arrival (in Migration table)
|
||||
if ($request->has('yearOfArrival') && $request->yearOfArrival !== 'all') {
|
||||
$year = $request->yearOfArrival;
|
||||
$query->whereHas('migration', function (Builder $query) use ($year) {
|
||||
$query->whereYear('date_of_arrival_aus', $year)
|
||||
->orWhereYear('date_of_arrival_nt', $year);
|
||||
});
|
||||
}
|
||||
|
||||
// Filter by Age at Migration (requires calculation)
|
||||
if ($request->has('ageAtMigration') && $request->ageAtMigration !== 'all') {
|
||||
$ageAtMigration = (int) $request->ageAtMigration;
|
||||
|
||||
$query->whereHas('migration', function (Builder $query) use ($ageAtMigration) {
|
||||
$query->whereRaw('YEAR(date_of_arrival_aus) - YEAR(person.date_of_birth) = ?', [$ageAtMigration])
|
||||
->orWhereRaw('YEAR(date_of_arrival_nt) - YEAR(person.date_of_birth) = ?', [$ageAtMigration]);
|
||||
});
|
||||
}
|
||||
|
||||
// Filter by settlement location (in Residence table)
|
||||
if ($request->has('settlementLocation') && $request->settlementLocation !== 'all') {
|
||||
$location = $request->settlementLocation;
|
||||
$query->whereHas('residence', function (Builder $query) use ($location) {
|
||||
$query->where(function ($q) use ($location) {
|
||||
$q->where('address', 'LIKE', "%{$location}%")
|
||||
->orWhere('suburb', 'LIKE', "%{$location}%")
|
||||
->orWhere('state', 'LIKE', "%{$location}%");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Paginate the results (default 10 per page, can be customized with the 'per_page' parameter)
|
||||
$perPage = $request->input('per_page', 10);
|
||||
$persons = $query->paginate($perPage);
|
||||
|
||||
// Eager load related models for the collection
|
||||
$persons->getCollection()->each->load([
|
||||
try {
|
||||
$person = Person::with([
|
||||
'migration',
|
||||
'naturalization',
|
||||
'residence',
|
||||
'family',
|
||||
'internment'
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => new PersonCollection($persons),
|
||||
'message' => 'Public search results retrieved successfully'
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
])->findOrFail($id);
|
||||
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to retrieve search results',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Find a person by ID card number.
|
||||
*
|
||||
* @param string $idCardNo
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function findByIdCard(string $idCardNo): JsonResponse
|
||||
{
|
||||
try {
|
||||
$person = Person::with(['migration', 'naturalization', 'residence', 'family', 'internment'])
|
||||
->where('id_card_no', $idCardNo)
|
||||
->first();
|
||||
|
||||
if (!$person) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Person not found with the provided ID card number'
|
||||
'message' => 'Record not found'
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => new PersonResource($person),
|
||||
'message' => 'Person found by ID card number'
|
||||
'message' => 'Record retrieved successfully'
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to retrieve person by ID card number',
|
||||
'message' => 'Failed to retrieve record',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
|
|
@ -187,12 +97,6 @@ class PersonController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param StorePersonRequest $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function store(StorePersonRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
|
|
@ -250,12 +154,6 @@ class PersonController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param string $id
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function show(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
|
|
@ -284,13 +182,6 @@ class PersonController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param UpdatePersonRequest $request
|
||||
* @param string $id
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function update(UpdatePersonRequest $request, string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
|
|
@ -371,12 +262,6 @@ class PersonController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param string $id
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,290 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Resources\PersonCollection;
|
||||
use App\Models\Person;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Exception;
|
||||
|
||||
class PublicSearchController extends Controller
|
||||
{
|
||||
/**
|
||||
* Search for persons with various filters
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function search(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$query = $this->buildSearchQuery($request);
|
||||
$persons = $this->paginateAndLoadRelations($query, $request);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => new PersonCollection($persons),
|
||||
'message' => 'Public search results retrieved successfully'
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to retrieve search results',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the search query with all filters applied
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Builder
|
||||
*/
|
||||
private function buildSearchQuery(Request $request): Builder
|
||||
{
|
||||
$query = Person::query();
|
||||
$useOrConditions = $this->shouldUseOrConditions($request);
|
||||
|
||||
// Apply basic filters (firstName, lastName, etc.)
|
||||
$this->applyBasicFilters($query, $request, $useOrConditions);
|
||||
|
||||
// Apply relation-based filters
|
||||
$this->applyYearOfArrivalFilter($query, $request);
|
||||
$this->applyAgeAtMigrationFilter($query, $request);
|
||||
$this->applySettlementLocationFilter($query, $request);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if OR logic should be used between filters
|
||||
*
|
||||
* @param Request $request
|
||||
* @return bool
|
||||
*/
|
||||
private function shouldUseOrConditions(Request $request): bool
|
||||
{
|
||||
$exactMatch = $request->boolean('exactMatch');
|
||||
$useOrLogic = $request->boolean('useOrLogic');
|
||||
|
||||
return $useOrLogic || $exactMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filters for fields in the Person table
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param Request $request
|
||||
* @param bool $useOrConditions
|
||||
* @return void
|
||||
*/
|
||||
private function applyBasicFilters(Builder $query, Request $request, bool $useOrConditions): void
|
||||
{
|
||||
$filters = $this->collectBasicFilters($request);
|
||||
|
||||
if (empty($filters)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($useOrConditions) {
|
||||
$this->applyFiltersWithOrLogic($query, $filters);
|
||||
} else {
|
||||
$this->applyFiltersWithAndLogic($query, $filters);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect basic filters from the request
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
private function collectBasicFilters(Request $request): array
|
||||
{
|
||||
$exactMatch = $request->boolean('exactMatch');
|
||||
$filters = [];
|
||||
|
||||
$filterFields = [
|
||||
'id_card_no' => 'id_card_no',
|
||||
'firstName' => 'christian_name',
|
||||
'lastName' => 'surname',
|
||||
'regionOfOrigin' => 'place_of_birth'
|
||||
];
|
||||
|
||||
foreach ($filterFields as $requestKey => $dbField) {
|
||||
if ($request->has($requestKey) && $request->input($requestKey) !== 'all') {
|
||||
$value = $request->input($requestKey);
|
||||
|
||||
if ($exactMatch) {
|
||||
// For exact matching, use raw query with BINARY for strict case-sensitive matching
|
||||
$filters[] = [$dbField, 'raw', $value];
|
||||
} else {
|
||||
// For partial matching, use standard LIKE with wildcards
|
||||
$filters[] = [$dbField, 'LIKE', "%{$value}%"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filters using OR logic
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param array $filters
|
||||
* @return void
|
||||
*/
|
||||
private function applyFiltersWithOrLogic(Builder $query, array $filters): void
|
||||
{
|
||||
$query->where(function ($q) use ($filters) {
|
||||
foreach ($filters as $index => $filter) {
|
||||
$method = $index === 0 ? 'where' : 'orWhere';
|
||||
|
||||
if ($filter[1] === 'raw') {
|
||||
// Handle raw exact matching (case-sensitive)
|
||||
$field = $filter[0];
|
||||
$value = $filter[2];
|
||||
$q->whereRaw("BINARY {$field} = ?", [$value]);
|
||||
} else {
|
||||
// Handle standard operators
|
||||
$q->{$method}($filter[0], $filter[1], $filter[2]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filters using AND logic
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param array $filters
|
||||
* @return void
|
||||
*/
|
||||
private function applyFiltersWithAndLogic(Builder $query, array $filters): void
|
||||
{
|
||||
foreach ($filters as $filter) {
|
||||
if ($filter[1] === 'raw') {
|
||||
// Handle raw exact matching (case-sensitive)
|
||||
$field = $filter[0];
|
||||
$value = $filter[2];
|
||||
$query->whereRaw("BINARY {$field} = ?", [$value]);
|
||||
} else {
|
||||
// Handle standard operators
|
||||
$query->where($filter[0], $filter[1], $filter[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by Year of Arrival
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param Request $request
|
||||
* @return void
|
||||
*/
|
||||
private function applyYearOfArrivalFilter(Builder $query, Request $request): void
|
||||
{
|
||||
if ($request->has('yearOfArrival') && $request->yearOfArrival !== 'all') {
|
||||
$year = $request->yearOfArrival;
|
||||
$query->whereHas('migration', function (Builder $subQuery) use ($year) {
|
||||
$subQuery->whereYear('date_of_arrival_aus', $year)
|
||||
->orWhereYear('date_of_arrival_nt', $year);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by Age at Migration
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param Request $request
|
||||
* @return void
|
||||
*/
|
||||
private function applyAgeAtMigrationFilter(Builder $query, Request $request): void
|
||||
{
|
||||
if ($request->has('ageAtMigration') && $request->ageAtMigration !== 'all') {
|
||||
$ageAtMigration = (int) $request->ageAtMigration;
|
||||
|
||||
$query->whereHas('migration', function (Builder $subQuery) use ($ageAtMigration) {
|
||||
$subQuery->whereRaw('YEAR(date_of_arrival_aus) - YEAR(person.date_of_birth) = ?', [$ageAtMigration])
|
||||
->orWhereRaw('YEAR(date_of_arrival_nt) - YEAR(person.date_of_birth) = ?', [$ageAtMigration]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by Settlement Location
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param Request $request
|
||||
* @return void
|
||||
*/
|
||||
private function applySettlementLocationFilter(Builder $query, Request $request): void
|
||||
{
|
||||
if ($request->has('settlementLocation') && $request->settlementLocation !== 'all') {
|
||||
$location = $request->settlementLocation;
|
||||
$query->whereHas('residence', function (Builder $subQuery) use ($location) {
|
||||
$subQuery->where('town_or_city', 'LIKE', "%{$location}%");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate results and load related models
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
private function paginateAndLoadRelations(Builder $query, Request $request): \Illuminate\Pagination\LengthAwarePaginator
|
||||
{
|
||||
$perPage = $request->input('per_page', 10);
|
||||
$persons = $query->paginate($perPage);
|
||||
|
||||
// Eager load related models
|
||||
$persons->getCollection()->each->load([
|
||||
'migration',
|
||||
'naturalization',
|
||||
'residence',
|
||||
'family',
|
||||
'internment'
|
||||
]);
|
||||
|
||||
return $persons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific migrant record by ID
|
||||
*
|
||||
* @param mixed $id
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getRecord($id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$person = Person::with([
|
||||
'migration',
|
||||
'naturalization',
|
||||
'residence',
|
||||
'family',
|
||||
'internment'
|
||||
])->findOrFail($id);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $person,
|
||||
'message' => 'Record retrieved successfully'
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Record not found',
|
||||
'error' => $e->getMessage()
|
||||
], 404);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class Cors
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$response = $next($request);
|
||||
|
||||
// Add CORS headers to the response
|
||||
$response->headers->set('Access-Control-Allow-Origin', '*');
|
||||
$response->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
|
||||
$response->headers->set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept, X-CSRF-TOKEN');
|
||||
$response->headers->set('Access-Control-Max-Age', '86400'); // 24 hours
|
||||
|
||||
// Handle preflight OPTIONS requests
|
||||
if ($request->isMethod('OPTIONS')) {
|
||||
return response()->json('', 200);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,10 +17,7 @@ class ResidenceResource extends JsonResource
|
|||
return $this->resource ? [
|
||||
'residence_id' => $this->residence_id,
|
||||
'person_id' => $this->person_id,
|
||||
'darwin' => $this->darwin,
|
||||
'katherine' => $this->katherine,
|
||||
'tennant_creek' => $this->tennant_creek,
|
||||
'alice_springs' => $this->alice_springs,
|
||||
'town_or_city' => $this->town_or_city,
|
||||
'home_at_death' => $this->home_at_death,
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
|
|
|
|||
|
|
@ -15,19 +15,11 @@ class Residence extends Model
|
|||
|
||||
protected $fillable = [
|
||||
'person_id',
|
||||
'darwin',
|
||||
'katherine',
|
||||
'tennant_creek',
|
||||
'alice_springs',
|
||||
'town_or_city',
|
||||
'home_at_death',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'darwin' => 'boolean',
|
||||
'katherine' => 'boolean',
|
||||
'tennant_creek' => 'boolean',
|
||||
'alice_springs' => 'boolean',
|
||||
];
|
||||
protected $casts = [];
|
||||
|
||||
// Relationship
|
||||
public function person()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use Illuminate\Foundation\Configuration\Exceptions;
|
|||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use App\Http\Middleware\Cors;
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
|
|
@ -14,7 +15,16 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware) {
|
||||
// Register Sanctum middleware for API authentication
|
||||
// Add CORS middleware globally to handle cross-origin requests
|
||||
$middleware->web(prepend: [
|
||||
Cors::class,
|
||||
]);
|
||||
|
||||
// Register Sanctum middleware for API authentication with CORS handling
|
||||
$middleware->api(prepend: [
|
||||
Cors::class, // Add CORS first to handle preflight requests
|
||||
]);
|
||||
|
||||
$middleware->api(append: [
|
||||
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('residence', function (Blueprint $table) {
|
||||
// Add the new town_or_city column
|
||||
$table->string('town_or_city', 100)->nullable()->after('person_id');
|
||||
|
||||
// Remove the boolean location columns
|
||||
$table->dropColumn([
|
||||
'darwin',
|
||||
'katherine',
|
||||
'tennant_creek',
|
||||
'alice_springs'
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('residence', function (Blueprint $table) {
|
||||
// Add back the boolean location columns
|
||||
$table->boolean('darwin')->default(false)->after('person_id');
|
||||
$table->boolean('katherine')->default(false)->after('darwin');
|
||||
$table->boolean('tennant_creek')->default(false)->after('katherine');
|
||||
$table->boolean('alice_springs')->default(false)->after('tennant_creek');
|
||||
|
||||
// Remove the town_or_city column
|
||||
$table->dropColumn('town_or_city');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -3,24 +3,16 @@
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\PersonController;
|
||||
use App\Http\Controllers\PublicSearchController;
|
||||
use App\Http\Controllers\AuthController;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| API Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here is where you can register API routes for your application. These
|
||||
| routes are loaded by the RouteServiceProvider and all of them will
|
||||
| be assigned to the "api" middleware group. Make something great!
|
||||
|
|
||||
*/
|
||||
|
||||
// Public routes - no authentication required
|
||||
Route::post('/login', [AuthController::class, 'login'])->name('login');
|
||||
|
||||
// Public search endpoint - allows searching without authentication
|
||||
Route::get('/persons/search', [PersonController::class, 'publicSearch'])->name('persons.public.search');
|
||||
// Public search endpoints - allow searching without authentication
|
||||
Route::get('/persons/search', [PublicSearchController::class, 'search'])->name('persons.public.search');
|
||||
|
||||
// Public endpoint to get a specific migrant's full record
|
||||
Route::get('/migrants/{person_id}', [PublicSearchController::class, 'getRecord'])->name('migrants.get');
|
||||
|
||||
// Protected routes - require Sanctum authentication
|
||||
Route::middleware('auth:sanctum')->group(function () {
|
||||
|
|
|
|||
|
|
@ -53,9 +53,8 @@ class PublicSearchApiTest extends TestCase
|
|||
|
||||
Residence::create([
|
||||
'person_id' => $person1->person_id,
|
||||
'address' => '123 Main St',
|
||||
'suburb' => 'Sydney',
|
||||
'state' => 'NSW',
|
||||
'town_or_city' => 'Sydney',
|
||||
'home_at_death' => 'Sydney, NSW',
|
||||
]);
|
||||
|
||||
// Person 2: Maria Mueller from Austria, arrived in 1885 at age 22, settled in Melbourne
|
||||
|
|
@ -77,9 +76,8 @@ class PublicSearchApiTest extends TestCase
|
|||
|
||||
Residence::create([
|
||||
'person_id' => $person2->person_id,
|
||||
'address' => '456 High St',
|
||||
'suburb' => 'Melbourne',
|
||||
'state' => 'VIC',
|
||||
'town_or_city' => 'Melbourne',
|
||||
'home_at_death' => 'Melbourne, VIC',
|
||||
]);
|
||||
|
||||
// Person 3: Robert Johnson from England, arrived in 1890 at age 30, settled in Brisbane
|
||||
|
|
@ -101,9 +99,8 @@ class PublicSearchApiTest extends TestCase
|
|||
|
||||
Residence::create([
|
||||
'person_id' => $person3->person_id,
|
||||
'address' => '789 Queen St',
|
||||
'suburb' => 'Brisbane',
|
||||
'state' => 'QLD',
|
||||
'town_or_city' => 'Brisbane',
|
||||
'home_at_death' => 'Brisbane, QLD',
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -194,17 +191,51 @@ class PublicSearchApiTest extends TestCase
|
|||
*/
|
||||
public function test_filter_by_settlement_location()
|
||||
{
|
||||
// Let's test with a simpler approach to avoid database-specific SQL issues
|
||||
// First, verify we have all records without filters
|
||||
$response = $this->getJson('/api/persons/search');
|
||||
$response->assertStatus(200)
|
||||
->assertJsonCount(3, 'data.data');
|
||||
|
||||
// Now test with a more specific test that shouldn't depend on SQL dialect
|
||||
$response = $this->getJson('/api/persons/search?lastName=Johnson');
|
||||
// Now test filtering by settlement location using the new town_or_city field
|
||||
$response = $this->getJson('/api/persons/search?settlementLocation=Sydney');
|
||||
$response->assertStatus(200)
|
||||
->assertJsonCount(1, 'data.data')
|
||||
->assertJsonPath('data.data.0.surname', 'Johnson');
|
||||
->assertJsonPath('data.data.0.surname', 'Smith');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the new town_or_city field stores and retrieves correctly through the API.
|
||||
*/
|
||||
public function test_town_or_city_field_works_correctly()
|
||||
{
|
||||
// Create a new person record with a residence
|
||||
$person = Person::create([
|
||||
'surname' => 'Darwin',
|
||||
'christian_name' => 'Charles',
|
||||
'full_name' => 'Charles Darwin',
|
||||
'date_of_birth' => '1809-02-12',
|
||||
'place_of_birth' => 'Shrewsbury, England',
|
||||
'occupation' => 'Naturalist',
|
||||
'id_card_no' => 'TEST-004'
|
||||
]);
|
||||
|
||||
// Create a residence record with the new town_or_city field
|
||||
$residence = Residence::create([
|
||||
'person_id' => $person->person_id,
|
||||
'town_or_city' => 'Darwin', // Using the town name as a test
|
||||
'home_at_death' => 'Down House, Kent'
|
||||
]);
|
||||
|
||||
// Retrieve the person through the API
|
||||
$response = $this->getJson("/api/migrants/{$person->person_id}");
|
||||
|
||||
// Assert the response contains the correct town_or_city value
|
||||
$response->assertStatus(200)
|
||||
->assertJson([
|
||||
'success' => true,
|
||||
'message' => 'Record retrieved successfully'
|
||||
])
|
||||
->assertJsonPath('data.residence.town_or_city', 'Darwin');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -232,6 +263,60 @@ class PublicSearchApiTest extends TestCase
|
|||
->assertJsonCount(3, 'data.data');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a migrant's full record can be retrieved by ID.
|
||||
*/
|
||||
public function test_get_migrant_by_id()
|
||||
{
|
||||
// Get a person ID from the test data
|
||||
$person = Person::where('id_card_no', 'TEST-001')->first();
|
||||
|
||||
// Call the endpoint
|
||||
$response = $this->getJson("/api/migrants/{$person->person_id}");
|
||||
|
||||
// Assert response structure and content
|
||||
$response->assertStatus(200)
|
||||
->assertJson([
|
||||
'success' => true,
|
||||
'message' => 'Record retrieved successfully'
|
||||
])
|
||||
->assertJsonStructure([
|
||||
'success',
|
||||
'data' => [
|
||||
'person_id',
|
||||
'surname',
|
||||
'christian_name',
|
||||
'full_name',
|
||||
'date_of_birth',
|
||||
'place_of_birth',
|
||||
'occupation',
|
||||
'id_card_no',
|
||||
// Related models
|
||||
'migration',
|
||||
'naturalization',
|
||||
'residence',
|
||||
'family',
|
||||
'internment'
|
||||
],
|
||||
'message'
|
||||
]);
|
||||
|
||||
// Verify the correct record was returned
|
||||
$response->assertJsonPath('data.id_card_no', 'TEST-001');
|
||||
$response->assertJsonPath('data.christian_name', 'John');
|
||||
$response->assertJsonPath('data.surname', 'Smith');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that requesting a non-existent migrant ID returns 404.
|
||||
*/
|
||||
public function test_get_nonexistent_migrant_returns_404()
|
||||
{
|
||||
$response = $this->getJson('/api/migrants/999999');
|
||||
|
||||
$response->assertStatus(404);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that using "all" as a filter value results in no filtering for that field.
|
||||
*/
|
||||
|
|
@ -302,4 +387,83 @@ class PublicSearchApiTest extends TestCase
|
|||
'message' => 'Unauthenticated.'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test strict case-insensitive matching with at least one correct field returning results
|
||||
* despite other incorrect fields. This validates OR logic in search when a special parameter is used.
|
||||
*/
|
||||
public function test_one_correct_field_with_multiple_incorrect()
|
||||
{
|
||||
// Create a special test person with unique attributes for this test
|
||||
$specialPerson = Person::create([
|
||||
'surname' => 'UniqueLastName',
|
||||
'christian_name' => 'UniqueFirstName',
|
||||
'full_name' => 'UniqueFirstName UniqueLastName',
|
||||
'date_of_birth' => '1930-05-15',
|
||||
'place_of_birth' => 'UniqueRegion, UniqueCountry',
|
||||
'occupation' => 'Developer',
|
||||
'id_card_no' => 'UNIQUE-ID-123'
|
||||
]);
|
||||
|
||||
Migration::create([
|
||||
'person_id' => $specialPerson->person_id,
|
||||
'date_of_arrival_aus' => '1960-03-20',
|
||||
'date_of_arrival_nt' => '1960-04-10',
|
||||
]);
|
||||
|
||||
Residence::create([
|
||||
'person_id' => $specialPerson->person_id,
|
||||
'town_or_city' => 'UniqueCity',
|
||||
'home_at_death' => 'UniqueCity, UniqueState',
|
||||
]);
|
||||
|
||||
// Test case-insensitive matching for id_card_no
|
||||
$response = $this->getJson('/api/persons/search?id_card_no=unique-id-123');
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJsonCount(1, 'data.data')
|
||||
->assertJsonPath('data.data.0.id_card_no', 'UNIQUE-ID-123');
|
||||
|
||||
// Test case-insensitive matching for firstName
|
||||
$response = $this->getJson('/api/persons/search?firstName=uniquefirstname');
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJsonCount(1, 'data.data')
|
||||
->assertJsonPath('data.data.0.christian_name', 'UniqueFirstName');
|
||||
|
||||
// Test case-insensitive matching for lastName
|
||||
$response = $this->getJson('/api/persons/search?lastName=uniquelastname');
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJsonCount(1, 'data.data')
|
||||
->assertJsonPath('data.data.0.surname', 'UniqueLastName');
|
||||
|
||||
// Test the OR logic where we provide one correct field and multiple incorrect ones
|
||||
// Using the useOrLogic parameter to apply OR condition instead of AND
|
||||
$response = $this->getJson('/api/persons/search?useOrLogic=true&id_card_no=unique-id-123&firstName=WrongName&lastName=WrongSurname');
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJsonCount(1, 'data.data')
|
||||
->assertJsonPath('data.data.0.id_card_no', 'UNIQUE-ID-123');
|
||||
|
||||
// Another OR logic test with a different correct field
|
||||
$response = $this->getJson('/api/persons/search?useOrLogic=true&id_card_no=WRONG-ID&firstName=uniquefirstname®ionOfOrigin=WrongRegion');
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJsonCount(1, 'data.data')
|
||||
->assertJsonPath('data.data.0.christian_name', 'UniqueFirstName');
|
||||
|
||||
// Test mixing capitalization in the correct field
|
||||
$response = $this->getJson('/api/persons/search?useOrLogic=true&id_card_no=WRONG-ID&firstName=WrongName&lastName=UniQueLastNAME');
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJsonCount(1, 'data.data')
|
||||
->assertJsonPath('data.data.0.surname', 'UniqueLastName');
|
||||
|
||||
// Verify that normal behavior without useOrLogic returns no results for mixed inputs
|
||||
$response = $this->getJson('/api/persons/search?id_card_no=unique-id-123&firstName=WrongName');
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJsonCount(0, 'data.data');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue