İlk temizlik tamamlandı bir önceki projeden

This commit is contained in:
Ümit Tunç
2026-04-28 21:14:25 +03:00
commit f80443aec0
10000 changed files with 959965 additions and 0 deletions
@@ -0,0 +1,579 @@
<?php
namespace App\Http\Controllers;
use App\Models\InspectionHistory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\File;
class InspectionHistoryController extends Controller
{
/**
* Upload inspection history (photo + comment)
*
* @group Inspection History
* @bodyParam table_name string required The database table name. Example: weld_logs
* @bodyParam record_id integer required The primary key of the record. Example: 15
* @bodyParam comment string required Inspection comment. Example: Approved.
* @bodyParam photo file[] nullable Photos to upload.
* @bodyParam module_slug string nullable Optional module slug to fetch settings. Example: daily-weld
* @bodyParam file_name_pattern string nullable Optional custom filename pattern.
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function upload(Request $request)
{
// Check authentication - using $request->user() for Sanctum compatibility
$u = $request->user();
if (!$u || !isset($u->level)) {
return response()->json([
'success' => false,
'message' => 'Authentication required'
], 401);
}
try {
// Validate the request
// Manually validate using Validator facade for conditional logic
$validator = \Illuminate\Support\Facades\Validator::make($request->all(), [
'table_name' => 'required|string',
'record_id' => 'required|numeric',
'comment' => 'required|string',
'module_slug' => 'nullable|string',
'photo' => 'nullable', // Allow both, will validate structure below
]);
// Add conditional validation for photo
$validator->after(function ($validator) use ($request) {
if ($request->hasFile('photo')) {
$photos = $request->file('photo');
if (is_array($photos)) {
// Validate array of photos
$photoValidator = \Illuminate\Support\Facades\Validator::make($request->all(), [
'photo.*' => 'image|mimes:jpg,jpeg,png,gif,webp|max:20480',
]);
if ($photoValidator->fails()) {
foreach ($photoValidator->errors()->all() as $error) {
$validator->errors()->add('photo', $error);
}
}
} else {
// Validate single photo
$photoValidator = \Illuminate\Support\Facades\Validator::make($request->all(), [
'photo' => 'image|mimes:jpg,jpeg,png,gif,webp|max:20480',
]);
if ($photoValidator->fails()) {
foreach ($photoValidator->errors()->all() as $error) {
$validator->errors()->add('photo', $error);
}
}
}
}
});
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => $validator->errors()->first()
], 422);
}
$validated = $validator->validated();
$tableName = $validated['table_name'];
$recordId = $validated['record_id'];
$comment = $validated['comment'];
$moduleSlug = $validated['module_slug'] ?? null;
// Get settings configuration if module_slug provided
$inspectionConfig = null;
$fileNamePattern = null;
$pathPattern = null;
if ($moduleSlug) {
$inspectionConfig = getInspectionHistoryConfig($moduleSlug);
// Get patterns from config
if ($inspectionConfig) {
$pathPattern = $inspectionConfig['path_pattern'] ?? null;
$fileNamePattern = $inspectionConfig['file_pattern'] ?? $inspectionConfig['pattern'] ?? null;
}
}
// Determine default path
$basePath = 'inspection_history'; // Default path
if ($inspectionConfig && isset($inspectionConfig['path'])) {
$basePath = $inspectionConfig['path'];
}
// Get row data for pattern processing (only needed once)
$rowData = null;
if (!empty($fileNamePattern) || !empty($pathPattern)) {
try {
$rowData = DB::table($tableName)->where('id', $recordId)->first();
if ($rowData) {
$rowData = (array) $rowData;
}
} catch (\Exception $e) {
\Log::warning("InspectionHistoryController: Could not fetch row data for pattern processing. Table: $tableName, ID: $recordId");
$rowData = null;
}
}
$createdRecords = [];
// Handle photos if provided
if ($request->hasFile('photo')) {
$photos = $request->file('photo');
// Ensure it's an array even if single file (though validation expects array)
if (!is_array($photos)) {
$photos = [$photos];
}
foreach ($photos as $photo) {
$filePath = null;
$fileName = null;
$fileSize = null;
// Generate file name using pattern or original name
$originalName = $photo->getClientOriginalName();
$originalExtension = $photo->getClientOriginalExtension();
// --- 1. Determine Folder Name (Path) ---
$folderName = '';
if (!empty($pathPattern)) {
// Process path pattern (allow slashes for subdirectories)
$folderName = $this->processFileNamePattern($pathPattern, $rowData, '', true);
// Sanitize but keep slashes
$folderName = preg_replace('/[^A-Za-z0-9_\-\/]/', '_', $folderName);
$folderName = preg_replace('/_+/', '_', $folderName);
$folderName = trim($folderName, '_');
}
// Fallback to record_id if folder name is empty
if (empty($folderName)) {
$folderName = ($moduleSlug === 'Other' && $recordId == 0) ? date('Y-m-d') : $recordId;
}
// --- 2. Determine File Name ---
$fileNameForSave = '';
if (!empty($fileNamePattern)) {
// Process file pattern (STRICT sanitization, no slashes)
$fileNameForSave = $this->processFileNamePattern($fileNamePattern, $rowData, $originalExtension, false);
}
// Fallback to original filename
if (empty($fileNameForSave)) {
$fileNameForSave = preg_replace('/[^A-Za-z0-9_\-\.]/', '_', pathinfo($originalName, PATHINFO_FILENAME));
if ($originalExtension) {
$fileNameForSave .= '.' . $originalExtension;
}
}
// --- 3. Save File ---
// Ensure unique filename - use pattern-based folder name
$folderPath = storage_path("documents/{$basePath}/{$folderName}");
if (!File::isDirectory($folderPath)) {
File::makeDirectory($folderPath, 0777, true, true);
}
$finalFileName = $fileNameForSave;
$counter = 1;
while (File::exists($folderPath . '/' . $finalFileName)) {
$baseName = pathinfo($fileNameForSave, PATHINFO_FILENAME);
$extension = pathinfo($fileNameForSave, PATHINFO_EXTENSION);
$finalFileName = "{$baseName}_{$counter}.{$extension}";
$counter++;
}
// Move file
$photo->move($folderPath, $finalFileName);
// Set file information
$filePath = "documents/{$basePath}/" . ($folderName !== '' ? "{$folderName}/" : "") . $finalFileName;
$fileName = $originalName; // Store original name in database
$fileSize = File::size($folderPath . '/' . $finalFileName);
// Create inspection history record for each photo
$inspectionHistory = InspectionHistory::create([
'table_name' => $tableName,
'record_id' => $recordId,
'column_name' => null,
'comment' => $comment,
'file_path' => $filePath,
'file_name' => $fileName,
'file_size' => $fileSize,
'user_id' => $u->id,
'ip_address' => $request->ip(),
]);
$createdRecords[] = $inspectionHistory;
}
} else {
// No photos, just comment
$inspectionHistory = InspectionHistory::create([
'table_name' => $tableName,
'record_id' => $recordId,
'column_name' => null,
'comment' => $comment,
'file_path' => null,
'file_name' => null,
'file_size' => null,
'user_id' => $u->id,
'ip_address' => $request->ip(),
]);
$createdRecords[] = $inspectionHistory;
}
return response()->json([
'success' => true,
'message' => 'Inspection history saved successfully',
'data' => $createdRecords
]);
} catch (\Exception $e) {
\Log::error("InspectionHistory upload error: " . $e->getMessage(), [
'trace' => $e->getTraceAsString()
]);
return response()->json([
'success' => false,
'message' => 'Failed to save inspection history: ' . $e->getMessage()
], 500);
}
}
/**
* List inspection history for a specific record
*
* @group Inspection History
* @queryParam table_name string required The database table name. Example: weld_logs
* @queryParam record_id integer required The primary key of the record. Example: 15
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function list(Request $request)
{
// Check authentication - using $request->user() for Sanctum compatibility
$u = $request->user();
if (!$u || !isset($u->level)) {
return response()->json([
'success' => false,
'message' => 'Authentication required'
], 401);
}
try {
$request->validate([
'table_name' => 'required|string',
'record_id' => 'required|numeric',
]);
$tableName = $request->input('table_name');
$recordId = $request->input('record_id');
// Get inspection history records with user information
$historyRecords = InspectionHistory::where('table_name', $tableName)
->where('record_id', $recordId)
->with('user:id,name')
->orderBy('created_at', 'DESC')
->get()
->map(function ($record) {
return [
'id' => $record->id,
'table_name' => $record->table_name,
'record_id' => $record->record_id,
'comment' => $record->comment,
'file_path' => $record->file_path,
'file_name' => $record->file_name,
'file_size' => $record->file_size,
'user_name' => $record->user ? $record->user->name : 'Unknown',
'user_id' => $record->user_id,
'ip_address' => $record->ip_address,
'created_at' => $record->created_at->format('Y-m-d H:i:s'),
];
});
return response()->json($historyRecords);
} catch (\Exception $e) {
\Log::error("InspectionHistory list error: " . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'Failed to retrieve inspection history: ' . $e->getMessage()
], 500);
}
}
/**
* Delete inspection history record
*
* @group Inspection History
* @urlParam id integer required The ID of the inspection history record. Example: 1
* @param int $id
* @return \Illuminate\Http\JsonResponse
*/
public function delete(Request $request, $id)
{
// Check authentication - using $request->user() for Sanctum compatibility
$u = $request->user();
if (!$u || !isset($u->level)) {
return response()->json([
'success' => false,
'message' => 'Authentication required'
], 401);
}
try {
$inspectionHistory = InspectionHistory::findOrFail($id);
// Delete file if exists
if ($inspectionHistory->file_path) {
$fullPath = storage_path($inspectionHistory->file_path);
if (File::exists($fullPath)) {
File::delete($fullPath);
}
}
// Delete record
$inspectionHistory->delete();
return response()->json([
'success' => true,
'message' => 'Inspection history deleted successfully'
]);
} catch (\Exception $e) {
\Log::error("InspectionHistory delete error: " . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'Failed to delete inspection history: ' . $e->getMessage()
], 500);
}
}
/**
* Bulk delete inspection history records
*
* @group Inspection History
* @bodyParam ids int[] required Array of inspection history IDs to delete. Example: [1, 2]
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function bulkDelete(Request $request)
{
// Check authentication
$u = $request->user();
if (!$u || !isset($u->level)) {
return response()->json([
'success' => false,
'message' => 'Authentication required'
], 401);
}
try {
$request->validate([
'ids' => 'required|array',
'ids.*' => 'numeric'
]);
$ids = $request->input('ids');
$deletedCount = 0;
foreach ($ids as $id) {
try {
$inspectionHistory = InspectionHistory::find($id);
if ($inspectionHistory) {
// Delete file if exists
if ($inspectionHistory->file_path) {
$fullPath = storage_path($inspectionHistory->file_path);
if (File::exists($fullPath)) {
File::delete($fullPath);
}
}
// Delete record
$inspectionHistory->delete();
$deletedCount++;
}
} catch (\Exception $e) {
\Log::error("InspectionHistory bulk delete error for ID $id: " . $e->getMessage());
// Continue with other records
}
}
return response()->json([
'success' => true,
'message' => "$deletedCount records deleted successfully",
'deleted_count' => $deletedCount
]);
} catch (\Exception $e) {
\Log::error("InspectionHistory bulk delete error: " . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'Failed to delete records: ' . $e->getMessage()
], 500);
}
}
/**
* List active inspection history modules
*
* @group Inspection History
* @return \Illuminate\Http\JsonResponse
*/
public function modules(Request $request)
{
// Check authentication
$u = $request->user();
if (!$u || !isset($u->level)) {
return response()->json([
'success' => false,
'message' => 'Authentication required'
], 401);
}
try {
// Get current settings
$currentSettingsJson = setting('inspection_history_management', false, '{}');
$currentSettings = json_decode($currentSettingsJson, true);
if (!is_array($currentSettings)) {
$currentSettings = [];
}
// Get all enabled modules from Types
$modules = \App\Types::where("enabled", true)
->whereNotNull("slug")
->where("slug", "!=", "")
->orderBy("s", "ASC")
->get(['id', 'slug', 'title']);
$activeModules = [];
foreach ($modules as $module) {
$slug = $module->slug;
$config = $currentSettings[$slug] ?? null;
$isActive = isset($config['active']) ? (bool)$config['active'] : false;
if ($isActive) {
$activeModules[] = [
'id' => $module->id,
'slug' => $module->slug,
'title' => $module->title,
'path_pattern' => $config['path_pattern'] ?? null,
'file_pattern' => $config['file_pattern'] ?? $config['pattern'] ?? null
];
}
}
return response()->json([
'success' => true,
'data' => $activeModules
]);
} catch (\Exception $e) {
\Log::error("InspectionHistory modules error: " . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'Failed to retrieve modules: ' . $e->getMessage()
], 500);
}
}
/**
* Process file name pattern and replace placeholders with row data
*
* @param string $pattern Pattern string with {column_name} placeholders
* @param array $rowData Row data array
* @param string $originalExtension Original file extension
* @param bool $allowSlashes Whether to allow slashes in the output (for directory paths)
* @return string Processed file name
*/
private function processFileNamePattern($pattern, $rowData, $originalExtension = '', $allowSlashes = false)
{
// Replace Date/Time placeholders first
$replacements = [
'{Y}' => date('Y'),
'{m}' => date('m'),
'{d}' => date('d'),
'{H}' => date('H'),
'{i}' => date('i'),
'{s}' => date('s'),
'{date}' => date('Y-m-d'),
'{time}' => date('H-i-s'),
'{timestamp}' => time(),
];
$processedPattern = str_replace(array_keys($replacements), array_values($replacements), $pattern);
// Find all placeholders in pattern
preg_match_all('/\{(\w+)\}/', $processedPattern, $matches);
$placeholders = $matches[1];
// Replace each placeholder with corresponding value from row data
foreach ($placeholders as $columnName) {
$value = isset($rowData[$columnName]) ? $rowData[$columnName] : '';
// Sanitize value for filename
$value = preg_replace('/[^A-Za-z0-9_\-\.]/', '_', (string)$value);
// Replace placeholder in pattern
$processedPattern = str_replace('{' . $columnName . '}', $value, $processedPattern);
}
// Check if extension is already in pattern
$hasExtension = preg_match('/\.[a-zA-Z0-9]+$/', $processedPattern);
// Add extension if not present and original extension exists (and not a path)
if (!$hasExtension && $originalExtension && !$allowSlashes) {
$processedPattern .= '.' . $originalExtension;
}
// Final sanitization
if ($allowSlashes) {
$processedPattern = preg_replace('/[^A-Za-z0-9_\-\.\/]/', '_', $processedPattern);
} else {
$processedPattern = preg_replace('/[^A-Za-z0-9_\-\.]/', '_', $processedPattern);
}
return $processedPattern;
}
/**
* Upload custom photo for "Other" module (no specific record required)
*
* @group Inspection History
* @bodyParam comment string required Inspection comment.
* @bodyParam photo file[] nullable Photos to upload.
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function uploadOther(Request $request)
{
// Set defaults for "Other" upload
$request->merge([
'table_name' => 'Other',
'record_id' => 0,
'module_slug' => 'Other'
]);
// Re-use existing upload logic
return $this->upload($request);
}
}