551 lines
21 KiB
PHP
551 lines
21 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Http\Requests\StorePersonRequest;
|
|
use App\Http\Requests\UpdatePersonRequest;
|
|
use App\Models\Person;
|
|
use App\Models\Photo;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Support\Str;
|
|
use Exception;
|
|
|
|
class MigrantController extends Controller
|
|
{
|
|
protected array $relations = ['migration', 'naturalization', 'residence', 'family', 'internment'];
|
|
|
|
public function index(Request $request): JsonResponse
|
|
{
|
|
try {
|
|
// Include photos in relations
|
|
$relations = [
|
|
'migration',
|
|
'naturalization',
|
|
'residence',
|
|
'family',
|
|
'internment',
|
|
'photos', // 🔥 Include photos
|
|
];
|
|
|
|
$query = Person::with($relations);
|
|
$joinedMigration = false;
|
|
|
|
// Filtering
|
|
$query->where(function ($outerQuery) use ($request) {
|
|
if ($request->filled('full_name')) {
|
|
$outerQuery->orWhere('full_name', 'LIKE', '%' . $request->input('full_name') . '%');
|
|
}
|
|
|
|
if ($request->filled('christian_name')) {
|
|
$outerQuery->orWhere('christian_name', 'LIKE', '%' . $request->input('christian_name') . '%');
|
|
}
|
|
|
|
if ($request->filled('surname')) {
|
|
$outerQuery->orWhere('surname', 'LIKE', '%' . $request->input('surname') . '%');
|
|
}
|
|
|
|
if ($request->filled('place_of_birth')) {
|
|
$outerQuery->orWhere('place_of_birth', 'LIKE', '%' . $request->input('place_of_birth') . '%');
|
|
}
|
|
|
|
if ($request->filled('occupation')) {
|
|
$outerQuery->orWhere('occupation', 'LIKE', '%' . $request->input('occupation') . '%');
|
|
}
|
|
|
|
if ($request->filled('date_of_birth')) {
|
|
$outerQuery->orWhereDate('date_of_birth', $request->input('date_of_birth'));
|
|
}
|
|
|
|
if ($request->filled('arrival_from') || $request->filled('arrival_to')) {
|
|
$outerQuery->orWhereHas('migration', function ($mq) use ($request) {
|
|
if ($request->filled('arrival_from') && $request->filled('arrival_to')) {
|
|
$mq->whereBetween('date_of_arrival_nt', [
|
|
$request->input('arrival_from'),
|
|
$request->input('arrival_to')
|
|
]);
|
|
} elseif ($request->filled('arrival_from')) {
|
|
$mq->where('date_of_arrival_nt', '>=', $request->input('arrival_from'));
|
|
} elseif ($request->filled('arrival_to')) {
|
|
$mq->where('date_of_arrival_nt', '<=', $request->input('arrival_to'));
|
|
}
|
|
});
|
|
}
|
|
|
|
if ($request->filled('town_or_city')) {
|
|
$outerQuery->orWhereHas('residence', function ($rq) use ($request) {
|
|
$rq->where('town_or_city', 'LIKE', '%' . $request->input('town_or_city') . '%');
|
|
});
|
|
}
|
|
});
|
|
|
|
// Sorting
|
|
$sortBy = $request->input('sort_by', 'created_at');
|
|
$sortOrder = $request->input('sort_order', 'desc');
|
|
$secondarySortBy = $request->input('secondary_sort_by');
|
|
$secondarySortOrder = $request->input('secondary_sort_order', 'asc');
|
|
|
|
if ($sortBy === 'date_of_arrival_nt') {
|
|
$query->leftJoin('migration', 'migration.person_id', '=', 'person.person_id');
|
|
$joinedMigration = true;
|
|
$query->select('person.*')->orderBy('migration.date_of_arrival_nt', $sortOrder);
|
|
} elseif (in_array($sortBy, ['full_name', 'christian_name', 'surname'])) {
|
|
$query->orderBy("person.$sortBy", $sortOrder);
|
|
} else {
|
|
$query->orderBy('person.created_at', 'desc');
|
|
}
|
|
|
|
if ($secondarySortBy === 'date_of_arrival_nt') {
|
|
if (!$joinedMigration) {
|
|
$query->leftJoin('migration', 'migration.person_id', '=', 'person.person_id');
|
|
$query->select('person.*');
|
|
}
|
|
$query->orderBy('migration.date_of_arrival_nt', $secondarySortOrder);
|
|
} elseif (in_array($secondarySortBy, ['full_name', 'christian_name', 'surname'])) {
|
|
$query->orderBy("person.$secondarySortBy", $secondarySortOrder);
|
|
}
|
|
|
|
// Pagination
|
|
$perPage = $request->input('per_page', 10);
|
|
$results = $query->paginate($perPage);
|
|
|
|
// Optionally map photo URLs (if you need full URLs or extra processing)
|
|
$results->getCollection()->transform(function ($person) {
|
|
$person->photos->transform(function ($photo) {
|
|
$photo->url = asset('storage/' . $photo->path); // or whatever logic you have
|
|
return $photo;
|
|
});
|
|
return $person;
|
|
});
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $results,
|
|
'message' => 'Persons retrieved successfully',
|
|
]);
|
|
} catch (Exception $e) {
|
|
\Log::error('Error retrieving persons: ' . $e->getMessage());
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Failed to retrieve persons',
|
|
'error' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
public function show(string $id): JsonResponse
|
|
{
|
|
try {
|
|
$person = Person::with(array_merge($this->relations, ['photos']))->find($id);
|
|
|
|
if ($person) {
|
|
// Add full URLs to photos
|
|
$person->photos->transform(function ($photo) {
|
|
$photo->url = asset('storage/' . $photo->path); // Adjust if needed
|
|
return $photo;
|
|
});
|
|
|
|
return $this->successResponse($person, 'Person retrieved successfully');
|
|
}
|
|
|
|
return $this->notFoundResponse('Person not found');
|
|
} catch (Exception $e) {
|
|
return $this->errorResponse('Failed to retrieve person', $e);
|
|
}
|
|
}
|
|
|
|
public function store(StorePersonRequest $request): JsonResponse
|
|
{
|
|
try {
|
|
$data = $request->only([
|
|
'surname', 'christian_name', 'date_of_birth', 'place_of_birth',
|
|
'date_of_death', 'occupation', 'additional_notes', 'reference', 'id_card_no'
|
|
]);
|
|
$data['full_name'] = trim("{$request->christian_name} {$request->surname}");
|
|
|
|
$person = Person::create($data);
|
|
|
|
// Handle related data
|
|
foreach ($this->relations as $relation) {
|
|
if ($request->has($relation)) {
|
|
$person->$relation()->create($request->$relation);
|
|
}
|
|
}
|
|
|
|
// Handle photo uploads
|
|
$uploadedPhotos = [];
|
|
if ($request->hasFile('photos')) {
|
|
$photoController = new PhotoController(); // Or inject this via DI
|
|
$uploadedPhotos = $photoController->handlePhotoUpload(
|
|
$request->file('photos'),
|
|
$person->person_id,
|
|
$request->input('captions', []),
|
|
$request->input('main_photo_index') // Pass the index directly
|
|
);
|
|
}
|
|
|
|
// Load all relations including photos
|
|
$person->load(array_merge($this->relations, ['photos']));
|
|
|
|
return $this->successResponse([
|
|
'person' => $person,
|
|
'uploaded_photos' => $uploadedPhotos
|
|
], 'Person created successfully', 201);
|
|
} catch (Exception $e) {
|
|
return $this->errorResponse('Failed to create person', $e);
|
|
}
|
|
}
|
|
|
|
public function update(UpdatePersonRequest $request, string $id): JsonResponse
|
|
{
|
|
try {
|
|
$person = Person::findOrFail($id);
|
|
|
|
$data = $request->only([
|
|
'surname', 'christian_name', 'date_of_birth', 'place_of_birth',
|
|
'date_of_death', 'occupation', 'additional_notes', 'reference', 'id_card_no'
|
|
]);
|
|
|
|
if ($request->hasAny(['christian_name', 'surname'])) {
|
|
$christian = $request->input('christian_name', $person->christian_name);
|
|
$surname = $request->input('surname', $person->surname);
|
|
$data['full_name'] = trim("$christian $surname");
|
|
}
|
|
|
|
$person->update($data);
|
|
|
|
foreach ($this->relations as $relation) {
|
|
if (is_array($request->$relation ?? null)) {
|
|
$person->$relation
|
|
? $person->$relation->update($request->$relation)
|
|
: $person->$relation()->create($request->$relation);
|
|
}
|
|
}
|
|
|
|
// Handle photo uploads during update
|
|
$uploadedPhotos = [];
|
|
if ($request->hasFile('photos')) {
|
|
$uploadedPhotos = $this->handlePhotoUpload($request, $person->person_id);
|
|
}
|
|
|
|
// Handle profile photo setting
|
|
if ($request->input('set_as_profile')) {
|
|
$profilePhotoSet = false;
|
|
|
|
// Try to set profile photo for new uploads first
|
|
if ($request->has('profile_photo_index')) {
|
|
$index = (int) $request->input('profile_photo_index');
|
|
|
|
if (isset($uploadedPhotos[$index])) {
|
|
// Clear all profile photos first
|
|
Photo::where('person_id', $person->person_id)->update(['is_profile_photo' => false]);
|
|
|
|
// Set the new uploaded photo as profile
|
|
$uploadedPhotos[$index]->is_profile_photo = true;
|
|
$uploadedPhotos[$index]->save();
|
|
$profilePhotoSet = true;
|
|
}
|
|
}
|
|
|
|
// If no new photo was set as profile, try existing photo
|
|
if (!$profilePhotoSet && $request->has('profile_photo_id')) {
|
|
$photoId = $request->input('profile_photo_id');
|
|
$photo = Photo::where('id', $photoId)->where('person_id', $person->person_id)->first();
|
|
|
|
if ($photo) {
|
|
// Clear all profile photos first
|
|
Photo::where('person_id', $person->person_id)->update(['is_profile_photo' => false]);
|
|
|
|
// Set the existing photo as profile
|
|
$photo->is_profile_photo = true;
|
|
$photo->save();
|
|
$profilePhotoSet = true;
|
|
}
|
|
}
|
|
|
|
// Log if profile photo couldn't be set (for debugging)
|
|
if (!$profilePhotoSet) {
|
|
\Log::warning('Failed to set profile photo', [
|
|
'person_id' => $person->person_id,
|
|
'profile_photo_index' => $request->input('profile_photo_index'),
|
|
'profile_photo_id' => $request->input('profile_photo_id'),
|
|
'uploaded_photos_count' => count($uploadedPhotos)
|
|
]);
|
|
}
|
|
}
|
|
|
|
// Handle existing photo updates (captions, etc.)
|
|
if ($request->has('existing_photos')) {
|
|
$existingPhotos = $request->input('existing_photos');
|
|
|
|
if (is_array($existingPhotos)) {
|
|
foreach ($existingPhotos as $photoData) {
|
|
if (isset($photoData['id'])) {
|
|
$photo = Photo::where('id', $photoData['id'])
|
|
->where('person_id', $person->person_id)
|
|
->first();
|
|
|
|
if ($photo && isset($photoData['caption'])) {
|
|
$photo->caption = $photoData['caption'];
|
|
$photo->save();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $this->successResponse([
|
|
'person' => $person->load(array_merge($this->relations, ['photos'])),
|
|
'uploaded_photos' => $uploadedPhotos
|
|
], 'Person updated successfully');
|
|
} catch (Exception $e) {
|
|
return $this->errorResponse('Failed to update person', $e);
|
|
}
|
|
}
|
|
|
|
public function destroy(string $id): JsonResponse
|
|
{
|
|
try {
|
|
$person = Person::with($this->relations)->findOrFail($id);
|
|
|
|
foreach ($this->relations as $relation) {
|
|
$person->$relation?->delete();
|
|
}
|
|
|
|
// Delete associated photos
|
|
if ($person->photos) {
|
|
foreach ($person->photos as $photo) {
|
|
$path = 'public/photos/' . $person->person_id . '/' . $photo->filename;
|
|
if (Storage::exists($path)) {
|
|
Storage::delete($path);
|
|
}
|
|
$photo->delete();
|
|
}
|
|
}
|
|
|
|
$person->delete();
|
|
|
|
return $this->successResponse(null, 'Person deleted successfully');
|
|
} catch (Exception $e) {
|
|
return $this->errorResponse('Failed to delete person', $e);
|
|
}
|
|
}
|
|
|
|
// ========== PHOTO MANAGEMENT METHODS ==========
|
|
|
|
/**
|
|
* Get all photos for a specific person
|
|
*/
|
|
public function getPhotos($personId): JsonResponse
|
|
{
|
|
try {
|
|
$person = Person::findOrFail($personId);
|
|
$photos = $person->photos()->get();
|
|
|
|
return $this->successResponse([
|
|
'photos' => $photos,
|
|
'profile_photo' => $person->photos()->where('is_profile_photo', true)->first()
|
|
], 'Photos retrieved successfully');
|
|
} catch (Exception $e) {
|
|
return $this->errorResponse('Failed to retrieve photos', $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Upload photos for a person
|
|
*/
|
|
public function uploadPhotos(Request $request, $personId): JsonResponse
|
|
{
|
|
$validator = Validator::make($request->all(), [
|
|
'photos' => 'required|array',
|
|
'photos.*' => 'required|image|max:10240', // Max 10MB per image
|
|
'captions' => 'nullable|array',
|
|
'captions.*' => 'nullable|string|max:255',
|
|
'set_as_profile' => 'nullable|boolean',
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'errors' => $validator->errors()
|
|
], 422);
|
|
}
|
|
|
|
try {
|
|
// Find the person
|
|
$person = Person::findOrFail($personId);
|
|
$uploadedPhotos = $this->handlePhotoUpload($request, $personId);
|
|
|
|
return $this->successResponse([
|
|
'uploaded_photos' => $uploadedPhotos,
|
|
'total_photos' => $person->photos()->count()
|
|
], 'Photos uploaded successfully');
|
|
} catch (Exception $e) {
|
|
return $this->errorResponse('Failed to upload photos', $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set a photo as the profile photo
|
|
*/
|
|
public function setAsProfilePhoto($photoId): JsonResponse
|
|
{
|
|
try {
|
|
$photo = Photo::findOrFail($photoId);
|
|
|
|
// Remove profile photo flag from other photos of the same person
|
|
Photo::where('person_id', $photo->person_id)
|
|
->update(['is_profile_photo' => false]);
|
|
|
|
// Set this photo as profile photo
|
|
$photo->is_profile_photo = true;
|
|
$result = $photo->save();
|
|
|
|
return $this->successResponse($photo,
|
|
$result ? 'Profile photo set successfully' : 'Failed to set profile photo');
|
|
} catch (Exception $e) {
|
|
return $this->errorResponse('Failed to set profile photo', $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update photo caption
|
|
*/
|
|
public function updatePhotoCaption(Request $request, $photoId): JsonResponse
|
|
{
|
|
$validator = Validator::make($request->all(), [
|
|
'caption' => 'required|string|max:255',
|
|
]);
|
|
|
|
if ($validator->fails()) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'errors' => $validator->errors()
|
|
], 422);
|
|
}
|
|
|
|
try {
|
|
$photo = Photo::findOrFail($photoId);
|
|
$photo->caption = $request->input('caption');
|
|
$photo->save();
|
|
|
|
return $this->successResponse($photo, 'Caption updated successfully');
|
|
} catch (Exception $e) {
|
|
return $this->errorResponse('Failed to update caption', $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete a photo
|
|
*/
|
|
public function deletePhoto($photoId): JsonResponse
|
|
{
|
|
try {
|
|
$photo = Photo::findOrFail($photoId);
|
|
|
|
// Delete the physical file
|
|
$path = 'public/photos/' . $photo->person_id . '/' . $photo->filename;
|
|
if (Storage::exists($path)) {
|
|
Storage::delete($path);
|
|
}
|
|
|
|
// If this was a profile photo, try to set another one
|
|
if ($photo->is_profile_photo) {
|
|
$nextPhoto = Photo::where('person_id', $photo->person_id)
|
|
->where('id', '!=', $photo->id)
|
|
->first();
|
|
|
|
if ($nextPhoto) {
|
|
$nextPhoto->is_profile_photo = true;
|
|
$nextPhoto->save();
|
|
}
|
|
}
|
|
|
|
// Delete the database record
|
|
$photo->delete();
|
|
|
|
return $this->successResponse(null, 'Photo deleted successfully');
|
|
} catch (Exception $e) {
|
|
return $this->errorResponse('Failed to delete photo', $e);
|
|
}
|
|
}
|
|
|
|
// ========== HELPER METHODS ==========
|
|
|
|
/**
|
|
* Handle photo upload for person creation/update
|
|
*/
|
|
private function handlePhotoUpload(Request $request, $personId): array
|
|
{
|
|
$uploadedPhotos = [];
|
|
|
|
if ($request->hasFile('photos')) {
|
|
$files = $request->file('photos');
|
|
$captions = $request->input('photo_captions', []);
|
|
$setAsProfile = $request->input('set_as_profile', false);
|
|
$profilePhotoSet = false;
|
|
|
|
foreach ($files as $index => $file) {
|
|
// Validate file
|
|
if (!$file->isValid() || !in_array($file->getMimeType(), ['image/jpeg', 'image/png', 'image/gif', 'image/webp'])) {
|
|
continue;
|
|
}
|
|
|
|
// Generate a unique filename
|
|
$extension = $file->getClientOriginalExtension();
|
|
$filename = Str::uuid() . '.' . $extension;
|
|
|
|
// Store the file
|
|
$path = $file->storeAs('photos/' . $personId, $filename, 'public');
|
|
|
|
// Create photo record
|
|
$photo = new Photo([
|
|
'person_id' => $personId,
|
|
'filename' => $filename,
|
|
'original_filename' => $file->getClientOriginalName(),
|
|
'file_path' => Storage::url($path),
|
|
'mime_type' => $file->getMimeType(),
|
|
'file_size' => $file->getSize() / 1024, // Convert to KB
|
|
'caption' => $captions[$index] ?? null,
|
|
'is_profile_photo' => false
|
|
]);
|
|
|
|
$photo->save();
|
|
$uploadedPhotos[] = $photo;
|
|
|
|
// Set as profile photo if requested and it's the first photo
|
|
if ($setAsProfile && !$profilePhotoSet) {
|
|
// Remove profile photo flag from other photos
|
|
Photo::where('person_id', $personId)->update(['is_profile_photo' => false]);
|
|
$photo->is_profile_photo = true;
|
|
$photo->save();
|
|
$profilePhotoSet = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $uploadedPhotos;
|
|
}
|
|
|
|
// Response helpers
|
|
protected function successResponse($data, string $message, int $status = 200): JsonResponse
|
|
{
|
|
return response()->json(['success' => true, 'data' => $data, 'message' => $message], $status);
|
|
}
|
|
|
|
protected function notFoundResponse(string $message): JsonResponse
|
|
{
|
|
return response()->json(['success' => false, 'message' => $message], 404);
|
|
}
|
|
|
|
protected function errorResponse(string $message, Exception $e): JsonResponse
|
|
{
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => $message,
|
|
'error' => $e->getMessage()
|
|
], 500);
|
|
}
|
|
} |