initial commit
This commit is contained in:
@@ -0,0 +1,455 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class AdminUserService
|
||||
{
|
||||
private const DEFAULT_PER_PAGE = 10;
|
||||
private const MAX_PER_PAGE = 100;
|
||||
|
||||
public function createUser(array $data): array
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$user = User::create([
|
||||
'name' => $data['name'],
|
||||
'email' => $data['email'],
|
||||
'password' => Hash::make($data['password']),
|
||||
'role' => $data['role'],
|
||||
'approval_status' => 'approved',
|
||||
'institution' => $data['institution'] ?? null,
|
||||
'gender' => $data['gender'] ?? null,
|
||||
'phone_number' => $data['phone_number'] ?? null,
|
||||
]);
|
||||
|
||||
if ($user->role === 'operator' && isset($data['disease_id'])) {
|
||||
$user->managedDiseases()->attach($data['disease_id']);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
LogActionService::logAction(
|
||||
auth()->id(),
|
||||
'create',
|
||||
'User',
|
||||
$user->id,
|
||||
$user->toArray(),
|
||||
"Created user: {$user->name}",
|
||||
true
|
||||
);
|
||||
return [true, 'User created successfully.', $user->toArray()];
|
||||
} catch (\Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
LogActionService::logAction(
|
||||
auth()->id(),
|
||||
'create',
|
||||
'User',
|
||||
null,
|
||||
[
|
||||
'name' => $data['name'] ?? null,
|
||||
'email' => $data['email'] ?? null,
|
||||
'error' => $exception->getMessage()
|
||||
],
|
||||
"Failed to create user: {$exception->getMessage()}",
|
||||
false
|
||||
);
|
||||
return [false, 'User creation failed: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
public function approveUser($id): array
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
$user = User::find($id);
|
||||
if (!$user) {
|
||||
DB::rollBack();
|
||||
LogActionService::logAction(
|
||||
$user,
|
||||
'edit',
|
||||
'User',
|
||||
$id,
|
||||
['error' => 'User not found'],
|
||||
"Failed to approve User: User not found",
|
||||
false
|
||||
);
|
||||
return [false, 'User not found.', []];
|
||||
}
|
||||
|
||||
$oldStatus = $user->approval_status;
|
||||
$user->approval_status = 'approved';
|
||||
$user->save();
|
||||
|
||||
DB::commit();
|
||||
|
||||
LogActionService::logAction(
|
||||
auth()->id(),
|
||||
'edit',
|
||||
'User',
|
||||
$user->id,
|
||||
[
|
||||
'old_status' => $oldStatus,
|
||||
'new_status' => 'approved'
|
||||
],
|
||||
"Approved user: {$user->name}",
|
||||
true
|
||||
);
|
||||
return [true, 'User approved successfully.', $user];
|
||||
} catch (\Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
LogActionService::logAction(
|
||||
auth()->id(),
|
||||
'edit',
|
||||
'User',
|
||||
$id,
|
||||
['error' => $exception->getMessage()],
|
||||
"Failed to approve user: {$exception->getMessage()}",
|
||||
false
|
||||
);
|
||||
|
||||
return [false, 'User approval failed: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
public function rejectUser($id): array
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
$user = User::find($id);
|
||||
if (!$user) {
|
||||
DB::rollBack();
|
||||
LogActionService::logAction(
|
||||
$user,
|
||||
'edit',
|
||||
'User',
|
||||
$id,
|
||||
['error' => 'User not found'],
|
||||
"Failed to reject User: User not found",
|
||||
false
|
||||
);
|
||||
return [false, 'User not found.', []];
|
||||
}
|
||||
|
||||
$oldStatus = $user->approval_status;
|
||||
$user->approval_status = 'rejected';
|
||||
$user->save();
|
||||
|
||||
DB::commit();
|
||||
|
||||
LogActionService::logAction(
|
||||
auth()->id(),
|
||||
'edit',
|
||||
'User',
|
||||
$user->id,
|
||||
[
|
||||
'old_status' => $oldStatus,
|
||||
'new_status' => 'rejected'
|
||||
],
|
||||
"Rejected user: {$user->name}",
|
||||
true
|
||||
);
|
||||
return [true, 'User rejected successfully.', $user];
|
||||
} catch (\Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
LogActionService::logAction(
|
||||
auth()->id(),
|
||||
'edit',
|
||||
'User',
|
||||
$id,
|
||||
['error' => $exception->getMessage()],
|
||||
"Failed to reject user: {$exception->getMessage()}",
|
||||
false
|
||||
);
|
||||
return [false, 'User rejection failed: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
public function editUser($id, array $data): array
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$user = User::find($id);
|
||||
if (!$user) {
|
||||
DB::rollBack();
|
||||
LogActionService::logAction(
|
||||
auth()->id(),
|
||||
'edit',
|
||||
'User',
|
||||
$id,
|
||||
['error' => 'User not found'],
|
||||
"Failed to edit user: User not found",
|
||||
false
|
||||
);
|
||||
return [false, 'User not found.', []];
|
||||
}
|
||||
if (($user->is_primary_admin)) {
|
||||
DB::rollBack();
|
||||
LogActionService::logAction(
|
||||
auth()->id(),
|
||||
'edit',
|
||||
'User',
|
||||
$id,
|
||||
['error' => 'Attempted to edit primary admin account'],
|
||||
"Failed to edit user: Cannot edit primary admin account",
|
||||
false
|
||||
);
|
||||
return [false, 'The primary admin account cannot be edited.', []];
|
||||
}
|
||||
|
||||
$oldData = $user->toArray();
|
||||
|
||||
if (isset($data['password']) && $data['password']) {
|
||||
$data['password'] = Hash::make($data['password']);
|
||||
} else {
|
||||
unset($data['password']);
|
||||
}
|
||||
|
||||
if (isset($data['email']) && $data['email'] !== $user->email) {
|
||||
$existingUser = User::where('email', $data['email'])->first();
|
||||
if ($existingUser) {
|
||||
return [false, 'Email address is already in use.', []];
|
||||
}
|
||||
}
|
||||
|
||||
$changedFields = array_keys(array_diff_assoc($data, $oldData));
|
||||
|
||||
// Get only the values that are actually changing
|
||||
|
||||
$user->update($data);
|
||||
|
||||
if ($user->role === 'operator' && isset($data['disease_id'])) {
|
||||
// Detach old disease (if any) and attach the new one
|
||||
$user->managedDiseases()->sync([$data['disease_id']]);
|
||||
}
|
||||
|
||||
$relevantOldData = array_intersect_key($oldData, $data);
|
||||
$relevantNewData = array_intersect_key($data, $oldData);
|
||||
|
||||
DB::commit();
|
||||
|
||||
LogActionService::logAction(
|
||||
auth()->id(),
|
||||
'edit',
|
||||
'User',
|
||||
$user->id,
|
||||
[
|
||||
'old' => $relevantOldData,
|
||||
'new' => $relevantNewData,
|
||||
'changed_fields' => $changedFields
|
||||
],
|
||||
"Updated user: {$user->name}",
|
||||
true
|
||||
);
|
||||
return [true, 'User updated successfully.', $user];
|
||||
} catch (\Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
LogActionService::logAction(
|
||||
auth()->id(),
|
||||
'edit',
|
||||
'User',
|
||||
$id,
|
||||
[
|
||||
'attempted_changes' => array_keys($data),
|
||||
'error' => $exception->getMessage()
|
||||
],
|
||||
"Failed to update user: {$exception->getMessage()}",
|
||||
false
|
||||
);
|
||||
return [false, 'User update failed: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function deleteUser($id): array
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
$user = User::find($id);
|
||||
if (!$user) {
|
||||
DB::rollBack();
|
||||
LogActionService::logAction(
|
||||
auth()->id(),
|
||||
'delete',
|
||||
'User',
|
||||
$id,
|
||||
['error' => 'User not found'],
|
||||
"Failed to delete user: User not found",
|
||||
false
|
||||
);
|
||||
return [false, 'User not found.', []];
|
||||
}
|
||||
|
||||
if ((int) auth()->id() === (int) $id) {
|
||||
DB::rollBack();
|
||||
LogActionService::logAction(
|
||||
auth()->id(),
|
||||
'delete',
|
||||
'User',
|
||||
$id,
|
||||
['error' => 'Attempted to delete own account'],
|
||||
"Failed to delete user: Cannot delete own account",
|
||||
false
|
||||
);
|
||||
return [false, 'You cannot delete your own account.', []];
|
||||
}
|
||||
|
||||
if ($user->is_primary_admin) {
|
||||
DB::rollBack();
|
||||
LogActionService::logAction(
|
||||
auth()->id(),
|
||||
'delete',
|
||||
'User',
|
||||
$id,
|
||||
['error' => 'Attempted to delete primary admin account'],
|
||||
"Failed to delete user: Cannot delete primary admin account",
|
||||
false
|
||||
);
|
||||
return [false, 'The primary admin account cannot be deleted.', []];
|
||||
}
|
||||
|
||||
$userInfo = [
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'role' => $user->role
|
||||
];
|
||||
|
||||
$user->delete();
|
||||
|
||||
DB::commit();
|
||||
|
||||
LogActionService::logAction(
|
||||
auth()->id(),
|
||||
'delete',
|
||||
'User',
|
||||
$id,
|
||||
$userInfo,
|
||||
"Deleted user: {$user->name}",
|
||||
true
|
||||
);
|
||||
return [true, 'User deleted successfully.', []];
|
||||
} catch (\Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
LogActionService::logAction(
|
||||
auth()->id(),
|
||||
'delete',
|
||||
'User',
|
||||
$id,
|
||||
['error' => $exception->getMessage()],
|
||||
"Failed to delete user: {$exception->getMessage()}",
|
||||
false
|
||||
);
|
||||
return [false, 'User deletion failed: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
public function getUsers(array $filters): array
|
||||
{
|
||||
try {
|
||||
$query = User::query();
|
||||
|
||||
$this->applyUserFilters($query, $filters);
|
||||
|
||||
$paginatedData = $this->paginateResults($query, $filters, 'users');
|
||||
|
||||
return [true, 'Users retrieved successfully.', $paginatedData];
|
||||
} catch (\Throwable $exception) {
|
||||
return [false, 'Failed to retrieve users: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getUserDetails($id): array
|
||||
{
|
||||
try {
|
||||
$user = User::findOrFail($id);
|
||||
return [true, 'User details retrieved successfully.', $user];
|
||||
} catch (\Throwable $exception) {
|
||||
// TO DO logging
|
||||
return [false, 'Failed to retrieve user details: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
public function assignOperatorToDiseases(int $userId, array $diseaseIds): array
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
$user = User::findOrFail($userId);
|
||||
|
||||
$user->managedDiseases()->sync($diseaseIds);
|
||||
|
||||
DB::commit();
|
||||
return [true, 'Diseases assigned to operator successfully.', $user];
|
||||
} catch (\Throwable $exception) {
|
||||
// Log exception
|
||||
DB::rollBack();
|
||||
return [false, 'Failed to assign diseases to operator: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
private function applyUserFilters(Builder $query, array $filters): void
|
||||
{
|
||||
if (!empty($filters['role'])) {
|
||||
$query->where('role', $filters['role']);
|
||||
}
|
||||
|
||||
if (!empty($filters['name'])) {
|
||||
$query->where('name', 'ILIKE', '%' . trim($filters['name']) . '%');
|
||||
}
|
||||
|
||||
if (!empty($filters['approval_status'])) {
|
||||
$query->where('approval_status', $filters['approval_status']);
|
||||
}
|
||||
|
||||
// Add sorting if needed
|
||||
$query->orderBy('updated_at', 'desc');
|
||||
}
|
||||
|
||||
private function paginateResults(Builder $query, array $filters, string $itemsKey): array
|
||||
{
|
||||
$perPage = isset($filters['per_page']) &&
|
||||
is_numeric($filters['per_page']) &&
|
||||
$filters['per_page'] > 0 &&
|
||||
$filters['per_page'] <= self::MAX_PER_PAGE
|
||||
? (int) $filters['per_page']
|
||||
: self::DEFAULT_PER_PAGE;
|
||||
|
||||
$page = isset($filters['page']) &&
|
||||
is_numeric($filters['page']) &&
|
||||
$filters['page'] > 0
|
||||
? (int) $filters['page']
|
||||
: 1;
|
||||
|
||||
$results = $query->paginate($perPage, ['*'], 'page', $page);
|
||||
|
||||
if ($results->isEmpty() && $results->currentPage() > 1) {
|
||||
$results = $query->paginate($perPage, ['*'], 'page', $results->lastPage());
|
||||
}
|
||||
|
||||
return [
|
||||
$itemsKey => $results->items(),
|
||||
'pagination' => [
|
||||
'current_page' => $results->currentPage(),
|
||||
'last_page' => $results->lastPage(),
|
||||
'per_page' => (int) $results->perPage(),
|
||||
'total' => $results->total(),
|
||||
'from' => $results->firstItem(),
|
||||
'to' => $results->lastItem(),
|
||||
'has_more_pages' => $results->hasMorePages(),
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class AuthUserService
|
||||
{
|
||||
public function register(
|
||||
string $name,
|
||||
string $email,
|
||||
string $password,
|
||||
?string $institution = null,
|
||||
?string $gender = null,
|
||||
?string $phoneNumber = null,
|
||||
?string $tujuanPermohonan = null
|
||||
): array {
|
||||
try {
|
||||
$user = User::create([
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
'password' => Hash::make($password),
|
||||
'institution' => $institution,
|
||||
'gender' => $gender,
|
||||
'phone_number' => $phoneNumber,
|
||||
'tujuan_permohonan' => $tujuanPermohonan,
|
||||
'role' => 'peneliti',
|
||||
'approval_status' => 'pending',
|
||||
]);
|
||||
|
||||
return [true, 'User registered successfully.', [
|
||||
'approval_status' => $user->approval_status,
|
||||
]];
|
||||
} catch (\Throwable $exception) {
|
||||
// TO DO logging
|
||||
return [false, 'Registration failed: ', []];
|
||||
}
|
||||
}
|
||||
|
||||
public function login(string $email, string $password): array
|
||||
{
|
||||
try {
|
||||
$user = User::where('email', $email)->first();
|
||||
|
||||
if (!$user || !Hash::check($password, $user->password)) {
|
||||
return [false, 'The provided credentials are incorrect.', []];
|
||||
}
|
||||
|
||||
$token = $user->createToken('MyApp')->plainTextToken;
|
||||
|
||||
$response = [
|
||||
'token' => $token,
|
||||
'approval_status' => $user->approval_status,
|
||||
'role' => $user->role,
|
||||
];
|
||||
|
||||
if ($user->role === 'operator') {
|
||||
$response['disease_id'] = $user->managedDiseases["disease_id"] ?? null;
|
||||
}
|
||||
|
||||
return [true, 'Login successful.', $response];
|
||||
} catch (\Throwable $exception) {
|
||||
// TO DO logging
|
||||
return [false, 'Login Failed: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
public function logout(User $user): void
|
||||
{
|
||||
$user->tokens()->delete();
|
||||
}
|
||||
|
||||
private function deleteTokenSanctum(int $userId): void
|
||||
{
|
||||
\Laravel\Sanctum\PersonalAccessToken::where('tokenable_id', $userId)
|
||||
->where('abilities', '["accessLoginMember"]')
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Comment;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class CommentService
|
||||
{
|
||||
public function createComment(array $data, int $userId): array
|
||||
{
|
||||
try {
|
||||
$comment = Comment::create([
|
||||
'disease_id' => $data['diseaseId'],
|
||||
'user_id' => $userId,
|
||||
'content' => $data['content'],
|
||||
'parent_id' => $data['parent_id'] ?? null,
|
||||
]);
|
||||
return [true, 'Comment created successfully.', $comment];
|
||||
} catch (\Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
return [false, 'Failed to create comment.', []];
|
||||
}
|
||||
}
|
||||
|
||||
public function editComment(int $commentId, array $data, int $userId): array
|
||||
{
|
||||
try{
|
||||
$comment = Comment::where('id', $commentId)->where('user_id', $userId)->first();
|
||||
|
||||
if (!$comment) {
|
||||
return [false, 'Comment not found or unauthorized.', []];
|
||||
}
|
||||
|
||||
$comment->update(['content' => $data['content']]);
|
||||
|
||||
return [true, 'Comment updated successfully.', $comment];
|
||||
} catch (\Exception $e){
|
||||
Log::error($e->getMessage());
|
||||
return [false, 'Failed to edit comment.', []];
|
||||
}
|
||||
}
|
||||
public function deleteComment(int $commentId, int $userId): array
|
||||
{
|
||||
try{
|
||||
$comment = Comment::where('id', $commentId)->where('user_id', $userId)->first();
|
||||
|
||||
if (!$comment) {
|
||||
return [false, 'Comment not found or unauthorized.', []];
|
||||
}
|
||||
|
||||
// Soft delete and mark as deleted
|
||||
$comment->update(['status' => 'deleted', 'content' => null]);
|
||||
$comment->delete();
|
||||
|
||||
return [true, 'Comment deleted successfully.', []];
|
||||
} catch (\Exception $e){
|
||||
Log::error($e->getMessage());
|
||||
return [false, 'Failed to delete comment.', []];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getComments(int $diseaseId): array
|
||||
{
|
||||
$comments = Comment::with([
|
||||
'replies' => function ($query) {
|
||||
$query->withTrashed(); // Include soft-deleted replies
|
||||
},
|
||||
'user:id,name,email,role', // Include user details for parent comments
|
||||
'replies.user:id,name,email,role' // Include user details for replies
|
||||
])
|
||||
->where('disease_id', $diseaseId)
|
||||
->whereNull('parent_id') // Fetch only parent comments
|
||||
->withTrashed() // Include soft-deleted parents
|
||||
->get()
|
||||
->map(function ($comment) {
|
||||
return [
|
||||
'id' => $comment->id,
|
||||
'content' => $comment->content_with_placeholder, // Use placeholder content for deleted comments
|
||||
'status' => $comment->status,
|
||||
'created_at' => $comment->created_at,
|
||||
'updated_at' => $comment->updated_at,
|
||||
'user' => $comment->user ? [
|
||||
'id' => $comment->user->id,
|
||||
'name' => $comment->user->name,
|
||||
'email' => $comment->user->email,
|
||||
'role' => $comment->user->role
|
||||
] : null,
|
||||
'replies' => $comment->replies->map(function ($reply) {
|
||||
return [
|
||||
'id' => $reply->id,
|
||||
'content' => $reply->content_with_placeholder,
|
||||
'status' => $reply->status,
|
||||
'created_at' => $reply->created_at,
|
||||
'updated_at' => $reply->updated_at,
|
||||
'user' => $reply->user ? [
|
||||
'id' => $reply->user->id,
|
||||
'name' => $reply->user->name,
|
||||
'email' => $reply->user->email,
|
||||
'role' => $reply->user->role
|
||||
] : null
|
||||
];
|
||||
})->toArray()
|
||||
];
|
||||
})->toArray(); // Convert to array for the response
|
||||
|
||||
return [true, 'Comments retrieved successfully.', $comments];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Disease;
|
||||
use App\Models\DiseaseRecord;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\Services\LogActionService;
|
||||
|
||||
class DiseaseRecordService
|
||||
{
|
||||
protected $diseaseService;
|
||||
protected $fileStorage;
|
||||
|
||||
private const DEFAULT_PER_PAGE = 10;
|
||||
private const MAX_PER_PAGE = 100;
|
||||
|
||||
public function __construct(
|
||||
DiseaseService $diseaseService,
|
||||
FileStorageService $fileStorage)
|
||||
{
|
||||
$this->diseaseService = $diseaseService;
|
||||
$this->fileStorage = $fileStorage;
|
||||
}
|
||||
|
||||
public function createDiseaseRecord(array $data, $userId): array
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$diseaseId = $data['diseaseId'];
|
||||
unset($data['diseaseId']);
|
||||
|
||||
[$schema, $message] = $this->diseaseService->getSchemaField($diseaseId);
|
||||
|
||||
foreach ($data['data'] as $key => $value) {
|
||||
|
||||
$fieldSchema = collect($schema)->firstWhere('name', $key);
|
||||
|
||||
if (!$fieldSchema) {
|
||||
throw new \Exception("Field '{$key}' is not defined in the schema.");
|
||||
}
|
||||
|
||||
if ($fieldSchema['type'] === 'file') {
|
||||
$isMultiple = $fieldSchema['multiple'] ?? false;
|
||||
|
||||
if ($isMultiple) {
|
||||
$value = is_array($value) ? $value : [$value];
|
||||
$fileUrls = [];
|
||||
foreach ($value as $index => $file) {
|
||||
if ($file && is_file($file)) {
|
||||
$fileUrls[] = $this->fileStorage->storeRecordFile(
|
||||
$file,
|
||||
$diseaseId,
|
||||
$key . '-' . ($index + 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$data['data'][$key] = $fileUrls;
|
||||
} else if (is_file($value)){
|
||||
$data['data'][$key] = $this->fileStorage->storeRecordFile($value, $diseaseId, $key);
|
||||
}
|
||||
} else {
|
||||
// Process non-file fields normally
|
||||
$data['data'][$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$diseaseRecord = DiseaseRecord::create([
|
||||
'disease_id' => $diseaseId,
|
||||
'data' => $data['data'],
|
||||
]);
|
||||
|
||||
|
||||
DB::commit();
|
||||
|
||||
LogActionService::logAction(
|
||||
$userId,
|
||||
'create',
|
||||
'DiseaseRecord',
|
||||
$diseaseRecord->id,
|
||||
$diseaseRecord->toArray(),
|
||||
"Created disease record for disease ID: {$diseaseId}",
|
||||
true
|
||||
);
|
||||
return [true, 'Disease record created successfully.', $diseaseRecord->toArray()];
|
||||
} catch (\Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
LogActionService::logAction(
|
||||
$userId,
|
||||
'create',
|
||||
'DiseaseRecord',
|
||||
null,
|
||||
[
|
||||
'disease_id' => $diseaseId ?? null,
|
||||
'error' => $exception->getMessage()
|
||||
],
|
||||
"Failed to create disease record: {$exception->getMessage()}",
|
||||
false
|
||||
);
|
||||
return [false, 'Disease record creation failed: ' . $exception->getMessage(), []];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function editDiseaseRecord($recordId, array $data, $userId): array
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
$diseaseRecord = DiseaseRecord::find($recordId);
|
||||
if (!$diseaseRecord) {
|
||||
DB::rollBack();
|
||||
LogActionService::logAction(
|
||||
$userId,
|
||||
'delete',
|
||||
'Disease',
|
||||
$recordId,
|
||||
['error' => 'Disease Record not found'],
|
||||
"Failed to delete disease record: Disease Record not found",
|
||||
false
|
||||
);
|
||||
return [false, 'Disease Record not found.', []];
|
||||
}
|
||||
$oldData = $diseaseRecord->toArray();
|
||||
$existingData = $diseaseRecord->data;
|
||||
|
||||
$diseaseId = $data['diseaseId'];
|
||||
unset($data['diseaseId']);
|
||||
|
||||
[$schema, $message] = $this->diseaseService->getSchemaField($diseaseId);
|
||||
|
||||
foreach ($data['data'] as $key => $value) {
|
||||
|
||||
$fieldSchema = collect($schema)->firstWhere('name', $key);
|
||||
|
||||
if (!$fieldSchema) {
|
||||
throw new \Exception("Field '{$key}' is not defined in the schema.");
|
||||
}
|
||||
|
||||
if ($fieldSchema['type'] === 'file') {
|
||||
$isMultiple = $fieldSchema['multiple'] ?? false;
|
||||
|
||||
if ($isMultiple) {
|
||||
$value = is_array($value) ? $value : [$value];
|
||||
$fileUrls = [];
|
||||
|
||||
// Delete old files if they exist
|
||||
if (isset($existingData[$key]) && is_array($existingData[$key])) {
|
||||
foreach ($existingData[$key] as $oldFile) {
|
||||
$this->fileStorage->deleteFile($oldFile, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Store new files
|
||||
foreach ($value as $index => $file) {
|
||||
if ($file && is_file($file)) {
|
||||
$fileUrls[] = $this->fileStorage->storeRecordFile(
|
||||
$file,
|
||||
$diseaseId,
|
||||
$key . '-' . ($index + 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$existingData[$key] = $fileUrls;
|
||||
} else {
|
||||
// Single file handling
|
||||
if (isset($existingData[$key])) {
|
||||
// echo($existingData[$key]);
|
||||
$this->fileStorage->deleteFile($existingData[$key], true);
|
||||
}
|
||||
|
||||
if (is_file($value)) {
|
||||
$existingData[$key] = $this->fileStorage->storeRecordFile(
|
||||
$value,
|
||||
$diseaseId,
|
||||
$key
|
||||
);
|
||||
} else {
|
||||
$existingData[$key] = null; // Clear the field if no new file is provided
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Non-file fields
|
||||
$existingData[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$diseaseRecord->update([
|
||||
'data' => $existingData,
|
||||
]);
|
||||
|
||||
DB::commit();
|
||||
|
||||
LogActionService::logAction(
|
||||
$userId,
|
||||
'edit',
|
||||
'DiseaseRecord',
|
||||
$diseaseRecord->id,
|
||||
[
|
||||
'old' => $oldData,
|
||||
'new' => $diseaseRecord->toArray(),
|
||||
'changed_fields' => array_keys($data['data'])
|
||||
],
|
||||
"Updated disease record ID: {$recordId}",
|
||||
true
|
||||
);
|
||||
return [true, 'Disease record updated successfully.', $diseaseRecord->toArray()];
|
||||
} catch (\Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
LogActionService::logAction(
|
||||
$userId,
|
||||
'edit',
|
||||
'DiseaseRecord',
|
||||
$recordId,
|
||||
[
|
||||
'attempted_changes' => array_keys($data['data']),
|
||||
'error' => $exception->getMessage()
|
||||
],
|
||||
"Failed to update disease record: {$exception->getMessage()}",
|
||||
false
|
||||
);
|
||||
|
||||
return [false, 'Disease record update failed: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteDiseaseRecord($recordId, $userId): array
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
$diseaseRecord = DiseaseRecord::find($recordId);
|
||||
$diseaseId = $diseaseRecord->disease_id;
|
||||
|
||||
if (!$diseaseRecord) {
|
||||
DB::rollBack();
|
||||
LogActionService::logAction(
|
||||
$userId,
|
||||
'delete',
|
||||
'Disease',
|
||||
$recordId,
|
||||
['error' => 'Disease Record not found'],
|
||||
"Failed to delete disease record: Disease Record not found",
|
||||
false
|
||||
);
|
||||
return [false, 'Disease Record not found.', []];
|
||||
}
|
||||
|
||||
$recordInfo = [
|
||||
'disease_id' => $diseaseId,
|
||||
'data_fields' => array_keys($diseaseRecord->data),
|
||||
'file_count' => collect($diseaseRecord->data)
|
||||
->flatten()
|
||||
->filter(function($value) {
|
||||
return is_string($value) && file_exists(storage_path("app/public/{$value}"));
|
||||
})
|
||||
->count()
|
||||
];
|
||||
|
||||
[$schema, $message] = $this->diseaseService->getSchemaField($diseaseId);
|
||||
$recordData = $diseaseRecord->data;
|
||||
|
||||
$this->deleteRecordFiles($recordData, $schema);
|
||||
|
||||
$diseaseRecord->delete();
|
||||
|
||||
DB::commit();
|
||||
|
||||
LogActionService::logAction(
|
||||
$userId,
|
||||
'delete',
|
||||
'DiseaseRecord',
|
||||
$recordId,
|
||||
$recordInfo,
|
||||
"Deleted disease record ID: {$recordId}",
|
||||
true
|
||||
);
|
||||
return [true, 'Disease record deleted successfully.', []];
|
||||
} catch (\Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
LogActionService::logAction(
|
||||
$userId,
|
||||
'delete',
|
||||
'DiseaseRecord',
|
||||
$recordId,
|
||||
['error' => $exception->getMessage()],
|
||||
"Failed to delete disease record: {$exception->getMessage()}",
|
||||
false
|
||||
);
|
||||
|
||||
return [false, 'Disease record deletion failed: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
private function transformFileFields(array $data, array $schema): array
|
||||
{
|
||||
foreach ($schema as $field) {
|
||||
// print_r($field);
|
||||
if ($field['type'] === 'file' && isset($data[$field['name']])) {
|
||||
// Handle single file
|
||||
if (empty($field['multiple'])) {
|
||||
$data[$field['name']] = $this->generatePublicFileUrl($data[$field['name']]);
|
||||
} else {
|
||||
// Handle multiple files
|
||||
$data[$field['name']] = array_map(function ($filePath) {
|
||||
return $this->generatePublicFileUrl($filePath);
|
||||
}, $data[$field['name']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a public URL for a given file path.
|
||||
*/
|
||||
private function generatePublicFileUrl(string $filePath): string
|
||||
{
|
||||
return Storage::url('public/' . $filePath);
|
||||
//return asset('storage/' . $filePath);
|
||||
}
|
||||
|
||||
public function getDiseaseRecords($diseaseId, array $filters)
|
||||
{
|
||||
try {
|
||||
[$schema, $message] = $this->diseaseService->getSchemaField($diseaseId);
|
||||
|
||||
$disease = Disease::findOrFail($diseaseId);
|
||||
|
||||
if (empty($schema)) {
|
||||
if ($message === 'Access denied. This disease is private.'){
|
||||
return [false, 'Cant Access Private Disease', []];
|
||||
}
|
||||
return [false, 'Schema not found for this disease.', []];
|
||||
}
|
||||
|
||||
$query = $this->buildDiseaseRecordQuery($diseaseId, $filters);
|
||||
$paginatedData = $this->paginateResults($query, $filters);
|
||||
|
||||
// Check if records exist and transform them
|
||||
$records = !empty($paginatedData['records'])
|
||||
? array_map(function ($record) use ($schema) {
|
||||
$record['data'] = $this->transformFileFields($record['data'], $schema);
|
||||
return $record;
|
||||
}, $paginatedData['records'])
|
||||
: [];
|
||||
|
||||
$response = [
|
||||
'name' => $disease['name'],
|
||||
'deskripsi' => $disease['deskripsi'],
|
||||
'schema' => $schema,
|
||||
'export_url' => $disease->export_url,
|
||||
'records' => $records,
|
||||
'pagination' => $paginatedData['pagination']
|
||||
];
|
||||
|
||||
|
||||
|
||||
return [true, 'Disease records retrieved successfully.', $response];
|
||||
} catch (\Throwable $exception) {
|
||||
return [false, 'Failed to retrieve disease records: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getDiseaseRecordDetails($diseaseId, $recordId): array
|
||||
{
|
||||
try {
|
||||
[$schema, $message] = $this->diseaseService->getSchemaField($diseaseId);
|
||||
|
||||
if (empty($schema)) {
|
||||
return [false, 'Schema not found for this disease.', []];
|
||||
}
|
||||
|
||||
$record = DiseaseRecord::where('disease_id', $diseaseId)
|
||||
->where('id', $recordId)
|
||||
->firstOrFail();
|
||||
|
||||
// Transform file fields in the record
|
||||
$record->data = $this->transformFileFields($record->data, $schema);
|
||||
|
||||
$response = [
|
||||
'schema' => $schema,
|
||||
'record' => $record->toArray(),
|
||||
];
|
||||
|
||||
return [true, 'Disease record details retrieved successfully.', $response];
|
||||
} catch (\Throwable $exception) {
|
||||
return [false, 'Failed to retrieve disease record details: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
private function buildDiseaseRecordQuery(int $diseaseId, array $filters): Builder
|
||||
{
|
||||
$query = DiseaseRecord::where('disease_id', $diseaseId);
|
||||
|
||||
// $this->applyDiseaseRecordFilters($query, $filters);
|
||||
if(!empty($filters['order_by'])){
|
||||
if($filters['order_by'] === 'desc'){
|
||||
$query->orderBy('created_at', 'desc');
|
||||
}else if($filters['order_by'] === 'desc'){
|
||||
$query->orderBy('created_at', 'asc');
|
||||
}
|
||||
}
|
||||
|
||||
$query->orderBy('updated_at', 'desc');
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
private function paginateResults(Builder $query, array $filters): array
|
||||
{
|
||||
$perPage = isset($filters['per_page']) &&
|
||||
is_numeric($filters['per_page']) &&
|
||||
$filters['per_page'] > 0 &&
|
||||
$filters['per_page'] <= self::MAX_PER_PAGE
|
||||
? (int) $filters['per_page']
|
||||
: self::DEFAULT_PER_PAGE;
|
||||
|
||||
$page = isset($filters['page']) &&
|
||||
is_numeric($filters['page']) &&
|
||||
$filters['page'] > 0
|
||||
? (int) $filters['page']
|
||||
: 1;
|
||||
|
||||
$results = $query->paginate($perPage, ['*'], 'page', $page);
|
||||
|
||||
if ($results->isEmpty() && $results->currentPage() > 1) {
|
||||
$results = $query->paginate($perPage, ['*'], 'page', $results->lastPage());
|
||||
}
|
||||
|
||||
return [
|
||||
'records' => $results->items(),
|
||||
'pagination' => [
|
||||
'current_page' => $results->currentPage(),
|
||||
'last_page' => $results->lastPage(),
|
||||
'per_page' => (int) $results->perPage(),
|
||||
'total' => $results->total(),
|
||||
'from' => $results->firstItem(),
|
||||
'to' => $results->lastItem(),
|
||||
'has_more_pages' => $results->hasMorePages(),
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
private function isFileArray(array $value): bool
|
||||
{
|
||||
return count(array_filter($value, function ($item) {
|
||||
return is_object($item) && method_exists($item, 'getClientOriginalExtension');
|
||||
})) > 0;
|
||||
}
|
||||
|
||||
public function deleteRecordFiles(array $data, array $schema): void
|
||||
{
|
||||
foreach ($data as $key => $value) {
|
||||
$fieldSchema = collect($schema)->firstWhere('name', $key);
|
||||
|
||||
// Only proceed if this is a file field
|
||||
if ($fieldSchema && $fieldSchema['type'] === 'file') {
|
||||
if (is_array($value)) {
|
||||
// Multiple files
|
||||
foreach ($value as $filePath) {
|
||||
$this->fileStorage->deleteFile($filePath, true);
|
||||
}
|
||||
} elseif (is_string($value)) {
|
||||
// Single file
|
||||
$this->fileStorage->deleteFile($value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Disease;
|
||||
use App\Http\Requests\CreateDiseaseRequest;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Services\LogActionService;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class DiseaseService
|
||||
{
|
||||
private const DEFAULT_PER_PAGE = 10;
|
||||
private const MAX_PER_PAGE = 100;
|
||||
|
||||
protected $fileStorage;
|
||||
|
||||
public function __construct(
|
||||
FileStorageService $fileStorage)
|
||||
{
|
||||
$this->fileStorage = $fileStorage;
|
||||
}
|
||||
|
||||
|
||||
public function createDisease(array $data, $userId): array
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$image_url = null;
|
||||
if (isset($data['cover_page']) && $data['cover_page']) {
|
||||
$disease = Disease::create([
|
||||
'name' => $data['name'],
|
||||
'deskripsi' => $data['deskripsi'],
|
||||
'visibilitas' => $data['visibilitas'],
|
||||
'schema' => $data['schema'],
|
||||
'cover_page' => null,
|
||||
]);
|
||||
|
||||
$image_url = $this->fileStorage->storeCoverImage($data['cover_page'], $disease->id);
|
||||
|
||||
$disease->update(['cover_page' => $image_url]);
|
||||
} else {
|
||||
$disease = Disease::create([
|
||||
'name' => $data['name'],
|
||||
'deskripsi' => $data['deskripsi'],
|
||||
'visibilitas' => $data['visibilitas'],
|
||||
'schema' => $data['schema'],
|
||||
'cover_page' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
LogActionService::logAction(
|
||||
$userId,
|
||||
'create',
|
||||
'Disease',
|
||||
$disease->id,
|
||||
$disease->toArray(),
|
||||
"Created disease: {$disease->name}",
|
||||
true
|
||||
);
|
||||
|
||||
return [true, 'Disease created successfully.', $disease->toArray()];
|
||||
} catch (\Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
LogActionService::logAction(
|
||||
$userId,
|
||||
'create',
|
||||
'Disease',
|
||||
null,
|
||||
[
|
||||
'name' => $data['name'] ?? null,
|
||||
'error' => $exception->getMessage()
|
||||
],
|
||||
"Failed to create disease: {$exception->getMessage()}",
|
||||
false
|
||||
);
|
||||
|
||||
return [false, 'Disease creation failed: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
public function editDisease($id, array $data, $userId): array
|
||||
{
|
||||
try {
|
||||
// var_dump($data);
|
||||
DB::beginTransaction();
|
||||
|
||||
$disease = Disease::find($id);
|
||||
if (!$disease) {
|
||||
DB::rollBack();
|
||||
LogActionService::logAction(
|
||||
$userId,
|
||||
'edit',
|
||||
'Disease',
|
||||
$id,
|
||||
['error' => 'Disease not found'],
|
||||
"Failed to edit disease: Disease not found",
|
||||
false
|
||||
);
|
||||
return [false, 'Disease not found.', []];
|
||||
}
|
||||
|
||||
$oldData = $disease->toArray();
|
||||
|
||||
if (isset($data['cover_page']) && $data['cover_page']) {
|
||||
if ($disease->cover_page) {
|
||||
$this->fileStorage->deleteFile($disease->cover_page, true);
|
||||
}
|
||||
|
||||
$data['cover_page'] = $this->fileStorage->storeCoverImage($data['cover_page'], $disease->id);
|
||||
} else {
|
||||
unset($data['cover_page']);
|
||||
}
|
||||
|
||||
$changedFields = array_keys(array_diff_assoc($data, $oldData));
|
||||
|
||||
// Get only the values that are actually changing
|
||||
$relevantOldData = array_intersect_key($oldData, $data);
|
||||
$relevantNewData = array_intersect_key($data, $oldData);
|
||||
|
||||
$disease->update($data);
|
||||
DB::commit();
|
||||
|
||||
LogActionService::logAction(
|
||||
$userId,
|
||||
'edit',
|
||||
'Disease',
|
||||
$disease->id,
|
||||
[
|
||||
'old' => $relevantOldData,
|
||||
'new' => $relevantNewData,
|
||||
'changed_fields' => $changedFields
|
||||
],
|
||||
"Updated disease: {$disease->name}",
|
||||
true
|
||||
);
|
||||
|
||||
return [true, 'Disease updated successfully.', $disease];
|
||||
} catch (\Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
LogActionService::logAction(
|
||||
$userId,
|
||||
'edit',
|
||||
'Disease',
|
||||
$id,
|
||||
[
|
||||
'attempted_changes' => array_keys($data),
|
||||
'error' => $exception->getMessage()
|
||||
],
|
||||
"Failed to update disease: {$exception->getMessage()}",
|
||||
false
|
||||
);
|
||||
return [false, 'Disease update failed: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function deleteDisease($id, $userId): array
|
||||
{
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
$disease = Disease::find($id);
|
||||
if (!$disease) {
|
||||
DB::rollBack();
|
||||
LogActionService::logAction(
|
||||
$userId,
|
||||
'delete',
|
||||
'Disease',
|
||||
$id,
|
||||
['error' => 'Disease not found'],
|
||||
"Failed to delete disease: Disease not found",
|
||||
false
|
||||
);
|
||||
return [false, 'Disease not found.', []];
|
||||
}
|
||||
|
||||
$diseaseInfo = [
|
||||
'name' => $disease->name,
|
||||
'had_cover_page' => !is_null($disease->cover_page),
|
||||
'schema_columns_count' => count($disease->schema['columns'] ?? [])
|
||||
];
|
||||
|
||||
if ($disease->cover_page) {
|
||||
$this->fileStorage->deleteFile($disease->cover_page, true);
|
||||
}
|
||||
|
||||
$directoryPath = "diseases/records/$id";
|
||||
$this->fileStorage->deleteDirectory($directoryPath, true);
|
||||
|
||||
$disease->delete();
|
||||
|
||||
DB::commit();
|
||||
LogActionService::logAction(
|
||||
$userId,
|
||||
'delete',
|
||||
'Disease',
|
||||
$id,
|
||||
$diseaseInfo,
|
||||
"Deleted disease: {$disease->name}",
|
||||
true
|
||||
);
|
||||
return [true, 'Disease deleted successfully.', []];
|
||||
} catch (\Throwable $exception) {
|
||||
DB::rollBack();
|
||||
|
||||
LogActionService::logAction(
|
||||
$userId,
|
||||
'delete',
|
||||
'Disease',
|
||||
$id,
|
||||
['error' => $exception->getMessage()],
|
||||
"Failed to delete disease: {$exception->getMessage()}",
|
||||
false
|
||||
);
|
||||
return [false, 'Disease deletion failed: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
public function getDisease(array $filters, $user): array
|
||||
{
|
||||
try {
|
||||
$query = Disease::query();
|
||||
$query->withCount('diseaseRecords');
|
||||
|
||||
if ($user->role === 'operator') {
|
||||
$query->where('id', $user->disease_id);
|
||||
} elseif ($user->role === 'peneliti') {
|
||||
$query->where('visibilitas', 'publik');
|
||||
}
|
||||
|
||||
$this->applyDiseaseFilters($query, $filters);
|
||||
|
||||
$paginatedData = $this->paginateResults($query, $filters, 'diseases');
|
||||
|
||||
return [true, 'Diseases retrieved successfully.', $paginatedData];
|
||||
} catch (\Throwable $exception) {
|
||||
return [false, 'Failed to retrieve diseases: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getDiseaseDetails($id): array
|
||||
{
|
||||
try {
|
||||
|
||||
$user = auth()->user();
|
||||
|
||||
$disease = Disease::withCount('diseaseRecords')->findOrFail($id);
|
||||
|
||||
if (!$disease) {
|
||||
return [false, 'Disease not found.', []];
|
||||
}
|
||||
|
||||
// Admins can access all diseases
|
||||
if ($user->role === 'admin') {
|
||||
return [true, 'Disease details retrieved successfully.', $disease];
|
||||
}
|
||||
|
||||
// Operators can only access their assigned diseases
|
||||
if ($user->role === 'operator') {
|
||||
$managesDisease = $user->managed_diseases['disease_id'];
|
||||
if ($managesDisease != $id) {
|
||||
return [false, 'Access denied. You are not assigned to this disease.', []];
|
||||
}
|
||||
}
|
||||
|
||||
// Researchers can only access public diseases
|
||||
if ($user->role === 'peneliti') {
|
||||
if ($user->approval_status !== 'approved') {
|
||||
return [false, 'Access denied. Your account is not approved.', []];
|
||||
}
|
||||
if ($disease->visibilitas !== 'publik') {
|
||||
return [false, 'Access denied. This disease is private.', []];
|
||||
}
|
||||
}
|
||||
|
||||
return [true, 'Disease details retrieved successfully.', $disease];
|
||||
} catch (\Throwable $exception) {
|
||||
return [false, 'Failed to retrieve disease details: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
public function getStatistics(array $filters): array
|
||||
{
|
||||
try {
|
||||
$query = Disease::query();
|
||||
$query->withCount('diseaseRecords');
|
||||
|
||||
$this->applyDiseaseFilters($query, $filters);
|
||||
|
||||
$diseases = $query->get(['id', 'name', 'disease_records_count']);
|
||||
|
||||
$totalDiseaseRecords = $diseases->sum('disease_records_count');
|
||||
|
||||
$stats = [
|
||||
'total_disease_records' => $totalDiseaseRecords,
|
||||
'diseases' => $diseases->map(function ($disease) {
|
||||
return [
|
||||
'id' => $disease->id,
|
||||
'name' => $disease->name,
|
||||
'disease_records_count' => $disease->disease_records_count,
|
||||
];
|
||||
}),
|
||||
];
|
||||
|
||||
return [true, 'Disease statistics retrieved successfully.', $stats];
|
||||
} catch (\Throwable $exception) {
|
||||
return [false, 'Failed to retrieve statistics: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getSchemaField(int $diseaseId): array
|
||||
{
|
||||
try {
|
||||
[$success, $message, $disease] = $this->getDiseaseDetails($diseaseId);
|
||||
|
||||
if (!$success) {
|
||||
return [[], $message];
|
||||
}
|
||||
|
||||
$schema = $disease->schema['columns'] ?? null;
|
||||
|
||||
if (!$schema) {
|
||||
return [[], $message];
|
||||
}
|
||||
|
||||
$formattedSchema = [];
|
||||
foreach ($schema as $column) {
|
||||
$field = [
|
||||
'name' => $column['name'] ?? null,
|
||||
'type' => $column['type'] ?? null,
|
||||
];
|
||||
|
||||
if ($field['type'] === 'file') {
|
||||
$field['format'] = $column['format'];
|
||||
|
||||
$field['multiple'] = isset($column['multiple']) ? (bool) $column['multiple'] : false; // Ensure multiple is set with default false
|
||||
}
|
||||
$field['is_visible'] = filter_var($column['is_visible'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
$formattedSchema[] = $field;
|
||||
}
|
||||
|
||||
return [$formattedSchema, 'Schema Retrieved Successfully'];
|
||||
} catch (\Throwable $exception) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private function applyDiseaseFilters(Builder $query, array $filters): void
|
||||
{
|
||||
if (!empty($filters['visibilitas'])) {
|
||||
$query->where('visibilitas', $filters['visibilitas']);
|
||||
}
|
||||
|
||||
if (!empty($filters['name'])) {
|
||||
$query->where('name', 'ILIKE', '%' . trim($filters['name']) . '%');
|
||||
}
|
||||
|
||||
if (!empty($filters['column_type'])) {
|
||||
$query->whereJsonContains('schema->columns', ['type' => $filters['column_type']]);
|
||||
}
|
||||
|
||||
if (!empty($filters['column_name'])) {
|
||||
$query->whereJsonContains('schema->columns', ['name' => $filters['column_name']]);
|
||||
}
|
||||
|
||||
//Sorting
|
||||
$query->orderBy('updated_at', 'desc');
|
||||
}
|
||||
|
||||
private function paginateResults(Builder $query, array $filters, string $itemsKey): array
|
||||
{
|
||||
$perPage = isset($filters['per_page']) &&
|
||||
is_numeric($filters['per_page']) &&
|
||||
$filters['per_page'] > 0 &&
|
||||
$filters['per_page'] <= self::MAX_PER_PAGE
|
||||
? (int) $filters['per_page']
|
||||
: self::DEFAULT_PER_PAGE;
|
||||
|
||||
$page = isset($filters['page']) &&
|
||||
is_numeric($filters['page']) &&
|
||||
$filters['page'] > 0
|
||||
? (int) $filters['page']
|
||||
: 1;
|
||||
|
||||
$results = $query->paginate($perPage, ['*'], 'page', $page);
|
||||
|
||||
if ($results->isEmpty() && $results->currentPage() > 1) {
|
||||
$results = $query->paginate($perPage, ['*'], 'page', $results->lastPage());
|
||||
}
|
||||
|
||||
return [
|
||||
$itemsKey => $results->items(),
|
||||
'pagination' => [
|
||||
'current_page' => $results->currentPage(),
|
||||
'last_page' => $results->lastPage(),
|
||||
'per_page' => (int) $results->perPage(),
|
||||
'total' => $results->total(),
|
||||
'from' => $results->firstItem(),
|
||||
'to' => $results->lastItem(),
|
||||
'has_more_pages' => $results->hasMorePages(),
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class FileStorageService
|
||||
{
|
||||
public function storeFile($file, string $directory, ?string $filename = null, bool $isPublic = false): string
|
||||
{
|
||||
if (!$filename) {
|
||||
$filename = Str::random(40) . '.' . $file->getClientOriginalExtension();
|
||||
}
|
||||
|
||||
$baseDirectory = $isPublic ? "public/$directory" : $directory;
|
||||
$path = $file->storeAs($baseDirectory, $filename);
|
||||
|
||||
return $isPublic ? str_replace('public/', '', $path) : $path;
|
||||
}
|
||||
|
||||
public function storeCoverImage($file, int $diseaseId): string
|
||||
{
|
||||
$filename = "disease-{$diseaseId}-cover." . $file->getClientOriginalExtension();
|
||||
return $this->storeFile($file, 'diseases/covers', $filename, true);
|
||||
}
|
||||
|
||||
public function storeRecordFile($file, int $diseaseId, string $fieldName): string
|
||||
{
|
||||
$filename = Str::slug($fieldName) . '-' . time() . '.' . $file->getClientOriginalExtension();
|
||||
return $this->storeFile($file, "diseases/records/$diseaseId", $filename, true);
|
||||
}
|
||||
|
||||
public function deleteFile(?string $path, bool $isPublic = false): bool
|
||||
{
|
||||
if (!$path) return true;
|
||||
|
||||
$fullPath = $isPublic ? 'public/' . $path : $path;
|
||||
return Storage::delete($fullPath);
|
||||
}
|
||||
|
||||
public function streamFile(string $filePath): StreamedResponse
|
||||
{
|
||||
return response()->streamDownload(function () use ($filePath) {
|
||||
$stream = fopen(storage_path('app/' . $filePath), 'rb');
|
||||
if ($stream) {
|
||||
fpassthru($stream);
|
||||
fclose($stream);
|
||||
} else {
|
||||
abort(404, 'File not found');
|
||||
}
|
||||
}, basename($filePath));
|
||||
}
|
||||
|
||||
public function deleteDirectory(string $directory, bool $isPublic = false): bool
|
||||
{
|
||||
$fullPath = $isPublic ? 'public/' . $directory : $directory;
|
||||
|
||||
$fullPathResolved = realpath(storage_path('app/' . $fullPath));
|
||||
$allowedDirectory = realpath(storage_path('app/diseases/'));
|
||||
|
||||
if (strpos($fullPathResolved, $allowedDirectory) !== 0) {
|
||||
throw new \Exception('Directory traversal attempt detected!');
|
||||
}
|
||||
|
||||
if(Storage::exists($fullPath)){
|
||||
Storage::deleteDirectory($fullPath);
|
||||
}
|
||||
|
||||
return !Storage::exists($fullPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Log;
|
||||
use Illuminate\Support\Facades\Log as LogFacade;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class LogActionService{
|
||||
|
||||
const MAX_PER_PAGE = 50;
|
||||
const DEFAULT_PER_PAGE = 10;
|
||||
|
||||
public static function logAction(int $userId,
|
||||
string $action,
|
||||
?string $model = null,
|
||||
?int $modelId = null,
|
||||
?array $changes = null,
|
||||
?string $description = null,
|
||||
$success)
|
||||
{
|
||||
try {
|
||||
Log::create([
|
||||
'user_id' => $userId,
|
||||
'action' => $action,
|
||||
'model' => $model,
|
||||
'model_id' => $modelId,
|
||||
'changes' => $changes ? json_encode($changes) : null,
|
||||
'description' => $description,
|
||||
'timestamp' => now(),
|
||||
'is_success' => $success,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
LogFacade::info('Error inserting log: ' . $e->getMessage());
|
||||
throw $e; // or handle it gracefully
|
||||
}
|
||||
}
|
||||
|
||||
public function getLogs(array $filters = []): array
|
||||
{
|
||||
try {
|
||||
$query = Log::query();
|
||||
$query->with(['user:id,name,role,email']);
|
||||
|
||||
$this->applyLogFilters($query, $filters);
|
||||
|
||||
$paginatedData = $this->paginateResults($query, $filters, 'logs');
|
||||
|
||||
return [true, 'Logs retrieved successfully.', $paginatedData];
|
||||
} catch (\Throwable $exception) {
|
||||
return [false, 'Failed to retrieve logs: ' . $exception->getMessage(), []];
|
||||
}
|
||||
}
|
||||
|
||||
private function applyLogFilters(Builder $query, array $filters): void
|
||||
{
|
||||
if (!empty($filters['user_id'])) {
|
||||
$query->where('user_id', $filters['user_id']);
|
||||
}
|
||||
|
||||
if (!empty($filters['action'])) {
|
||||
$query->where('action', $filters['action']);
|
||||
}
|
||||
|
||||
if (!empty($filters['model'])) {
|
||||
$query->where('model', $filters['model']);
|
||||
}
|
||||
|
||||
if (!empty($filters['date_from'])) {
|
||||
$query->whereDate('timestamp', '>=', $filters['date_from']);
|
||||
}
|
||||
|
||||
if (!empty($filters['date_to'])) {
|
||||
$query->whereDate('timestamp', '<=', $filters['date_to']);
|
||||
}
|
||||
|
||||
if (isset($filters['is_success'])) {
|
||||
|
||||
$query->where('is_success', $filters['is_success']);
|
||||
// $successFilter = filter_var($filters['is_success'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
// if (!is_null($successFilter)) {
|
||||
// $query->where('is_success', $successFilter);
|
||||
// }
|
||||
}
|
||||
|
||||
// Default sorting by timestamp
|
||||
$query->orderBy('timestamp', 'desc');
|
||||
}
|
||||
|
||||
private function paginateResults(Builder $query, array $filters, string $itemsKey): array
|
||||
{
|
||||
$perPage = isset($filters['per_page']) &&
|
||||
is_numeric($filters['per_page']) &&
|
||||
$filters['per_page'] > 0 &&
|
||||
$filters['per_page'] <= self::MAX_PER_PAGE
|
||||
? (int)$filters['per_page']
|
||||
: self::DEFAULT_PER_PAGE;
|
||||
|
||||
$page = isset($filters['page']) &&
|
||||
is_numeric($filters['page']) &&
|
||||
$filters['page'] > 0
|
||||
? (int)$filters['page']
|
||||
: 1;
|
||||
|
||||
$results = $query->paginate($perPage, ['*'], 'page', $page);
|
||||
|
||||
if ($results->isEmpty() && $results->currentPage() > 1) {
|
||||
$results = $query->paginate($perPage, ['*'], 'page', $results->lastPage());
|
||||
}
|
||||
|
||||
return [
|
||||
$itemsKey => $results->items(),
|
||||
'pagination' => [
|
||||
'current_page' => $results->currentPage(),
|
||||
'last_page' => $results->lastPage(),
|
||||
'per_page' => (int)$results->perPage(),
|
||||
'total' => $results->total(),
|
||||
'from' => $results->firstItem(),
|
||||
'to' => $results->lastItem(),
|
||||
'has_more_pages' => $results->hasMorePages(),
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user