update
This commit is contained in:
No files matched your search
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class BackupRestoreLatestCommand extends Command
|
||||
{
|
||||
protected $signature = 'backup:restore-latest
|
||||
{--url= : Base URL server cloud backup}
|
||||
{--token= : Token API cloud backup}
|
||||
{--name= : Nama backup}
|
||||
{--output= : Lokasi file download}
|
||||
{--file= : Pakai file backup lokal, tanpa download}
|
||||
{--download-only : Hanya download, tidak restore}
|
||||
{--host= : Host PostgreSQL target}
|
||||
{--port= : Port PostgreSQL target}
|
||||
{--database= : Database PostgreSQL target}
|
||||
{--username= : Username PostgreSQL target}
|
||||
{--password= : Password PostgreSQL target, opsional jika .pgpass sudah ada}
|
||||
{--clean=1 : Jalankan pg_restore --clean}
|
||||
{--if-exists=1 : Jalankan pg_restore --if-exists}
|
||||
{--no-owner=1 : Jalankan pg_restore --no-owner}
|
||||
{--yes : Lewati konfirmasi restore}';
|
||||
|
||||
protected $description = 'Download backup terbaru dari duidev.com dan restore penuh ke PostgreSQL.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$backupName = (string) ($this->option('name') ?: config('cloud_backup.backup_name', 'lismikro'));
|
||||
$file = $this->option('file')
|
||||
? (string) $this->option('file')
|
||||
: $this->downloadLatest($backupName);
|
||||
|
||||
if (!$file || !File::exists($file)) {
|
||||
$this->error('File backup tidak ditemukan.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$this->info('File backup siap: ' . $file);
|
||||
|
||||
if ($this->option('download-only')) {
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$host = (string) ($this->option('host') ?: config('cloud_backup.db_host'));
|
||||
$port = (string) ($this->option('port') ?: config('cloud_backup.db_port'));
|
||||
$database = (string) ($this->option('database') ?: config('cloud_backup.db_database'));
|
||||
$username = (string) ($this->option('username') ?: config('cloud_backup.db_username'));
|
||||
|
||||
if (!$this->option('yes') && !$this->confirm("Restore penuh ke database {$database} di {$host}:{$port}?", false)) {
|
||||
$this->warn('Restore dibatalkan.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$command = [
|
||||
(string) config('cloud_backup.pg_restore', 'pg_restore'),
|
||||
'-h', $host,
|
||||
'-p', $port,
|
||||
'-U', $username,
|
||||
'-d', $database,
|
||||
'-v',
|
||||
];
|
||||
|
||||
if ((int) $this->option('clean') === 1) {
|
||||
$command[] = '--clean';
|
||||
}
|
||||
|
||||
if ((int) $this->option('if-exists') === 1) {
|
||||
$command[] = '--if-exists';
|
||||
}
|
||||
|
||||
if ((int) $this->option('no-owner') === 1) {
|
||||
$command[] = '--no-owner';
|
||||
}
|
||||
|
||||
$command[] = $file;
|
||||
|
||||
$env = null;
|
||||
$password = (string) ($this->option('password') ?: config('cloud_backup.db_password'));
|
||||
if ($password !== '') {
|
||||
$env = ['PGPASSWORD' => $password];
|
||||
}
|
||||
|
||||
$process = new Process($command, base_path(), $env, null, null);
|
||||
$process->setTimeout(null);
|
||||
|
||||
$this->info('Menjalankan pg_restore...');
|
||||
$process->run(function (string $type, string $buffer): void {
|
||||
$this->output->write($buffer);
|
||||
});
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
$this->error('Restore gagal dengan kode: ' . $process->getExitCode());
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$this->info('Restore selesai.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function downloadLatest(string $backupName): ?string
|
||||
{
|
||||
$baseUrl = rtrim((string) ($this->option('url') ?: config('cloud_backup.api_url')), '/');
|
||||
$token = (string) ($this->option('token') ?: config('cloud_backup.api_token'));
|
||||
|
||||
if ($baseUrl === '' || $token === '') {
|
||||
$this->error('CLOUD_BACKUP_API_URL dan CLOUD_BACKUP_API_TOKEN wajib diisi.');
|
||||
return null;
|
||||
}
|
||||
|
||||
$output = $this->option('output')
|
||||
? (string) $this->option('output')
|
||||
: (string) config('cloud_backup.download_path');
|
||||
|
||||
File::ensureDirectoryExists(dirname($output), 0750, true);
|
||||
|
||||
$metadata = Http::withToken($token)
|
||||
->timeout(60)
|
||||
->get($baseUrl . '/api/backups/latest', ['backup_name' => $backupName]);
|
||||
|
||||
if (!$metadata->successful()) {
|
||||
$this->error('Gagal membaca metadata backup terbaru. HTTP ' . $metadata->status());
|
||||
$this->line($metadata->body());
|
||||
return null;
|
||||
}
|
||||
|
||||
$info = $metadata->json();
|
||||
$this->info('Backup terbaru: ' . ($info['file'] ?? '-'));
|
||||
|
||||
$download = Http::withToken($token)
|
||||
->timeout(0)
|
||||
->sink($output)
|
||||
->get($baseUrl . '/api/backups/latest/download', ['backup_name' => $backupName]);
|
||||
|
||||
if (!$download->successful()) {
|
||||
File::delete($output);
|
||||
$this->error('Gagal download backup. HTTP ' . $download->status());
|
||||
$this->line($download->body());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!empty($info['sha256']) && hash_file('sha256', $output) !== strtolower((string) $info['sha256'])) {
|
||||
File::delete($output);
|
||||
$this->error('Checksum file download tidak sesuai.');
|
||||
return null;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class BackupUploadFileCommand extends Command
|
||||
{
|
||||
protected $signature = 'backup:upload-file
|
||||
{file : Path file .backup yang akan dikirim}
|
||||
{--url= : Base URL server cloud backup}
|
||||
{--token= : Token API cloud backup}
|
||||
{--name= : Nama backup}
|
||||
{--chunk-size= : Ukuran chunk dalam byte}';
|
||||
|
||||
protected $description = 'Upload file backup ke duidev.com secara bertahap/chunked.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$file = (string) $this->argument('file');
|
||||
if (!is_file($file) || !is_readable($file)) {
|
||||
$this->error('File backup tidak ditemukan atau tidak bisa dibaca: ' . $file);
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$baseUrl = rtrim((string) ($this->option('url') ?: config('cloud_backup.api_url')), '/');
|
||||
$token = (string) ($this->option('token') ?: config('cloud_backup.api_token'));
|
||||
$backupName = (string) ($this->option('name') ?: config('cloud_backup.backup_name', 'lismikro'));
|
||||
$chunkSize = (int) ($this->option('chunk-size') ?: config('cloud_backup.chunk_size', 5 * 1024 * 1024));
|
||||
|
||||
if ($baseUrl === '' || $token === '') {
|
||||
$this->error('CLOUD_BACKUP_API_URL dan CLOUD_BACKUP_API_TOKEN wajib diisi.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if ($chunkSize < 1024 * 1024) {
|
||||
$this->error('Chunk size minimal 1 MB.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$size = filesize($file);
|
||||
$checksum = hash_file('sha256', $file);
|
||||
$totalChunks = (int) ceil($size / $chunkSize);
|
||||
$uploadId = now()->format('YmdHis') . '_' . Str::random(16);
|
||||
$endpoint = $baseUrl . '/api/backups/chunk';
|
||||
|
||||
$this->info('Upload backup: ' . basename($file));
|
||||
$this->info('Total chunk: ' . $totalChunks);
|
||||
|
||||
$handle = fopen($file, 'rb');
|
||||
if (!$handle) {
|
||||
$this->error('Gagal membuka file backup.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
try {
|
||||
for ($index = 0; $index < $totalChunks; $index++) {
|
||||
$chunk = fread($handle, $chunkSize);
|
||||
if ($chunk === false) {
|
||||
$this->error('Gagal membaca chunk ke-' . $index);
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$response = Http::withToken($token)
|
||||
->timeout(300)
|
||||
->attach('chunk', $chunk, 'chunk_' . $index . '.part')
|
||||
->post($endpoint, [
|
||||
'backup_name' => $backupName,
|
||||
'upload_id' => $uploadId,
|
||||
'filename' => basename($file),
|
||||
'chunk_index' => $index,
|
||||
'total_chunks' => $totalChunks,
|
||||
'total_size' => $size,
|
||||
'checksum' => $checksum,
|
||||
]);
|
||||
|
||||
if (!$response->successful()) {
|
||||
$this->error('Upload chunk ke-' . $index . ' gagal. HTTP ' . $response->status());
|
||||
$this->line($response->body());
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$this->line('Chunk ' . ($index + 1) . '/' . $totalChunks . ' terkirim.');
|
||||
}
|
||||
} finally {
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
$this->info('Upload backup selesai.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Periksa;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class ExportRegisterApiByDateCommand extends Command
|
||||
{
|
||||
protected $signature = 'register-api:export
|
||||
{date : Tanggal data periksa, format YYYY-MM-DD}
|
||||
{--column=daftar : Kolom tanggal untuk filter, contoh daftar/tanggalregis/tanggalsampel}
|
||||
{--output= : Simpan hasil JSON ke file}
|
||||
{--pretty : Format JSON agar mudah dibaca}';
|
||||
|
||||
protected $description = 'Export data periksa per tanggal ke format JSON payload registerApi.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$date = $this->argument('date');
|
||||
$column = (string) $this->option('column');
|
||||
|
||||
if (!$this->isValidDate($date)) {
|
||||
$this->error('Format tanggal harus YYYY-MM-DD.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if (!in_array($column, ['daftar', 'tanggalregis', 'tanggalsampel'], true)) {
|
||||
$this->error('Kolom tanggal hanya boleh: daftar, tanggalregis, tanggalsampel.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$records = Periksa::with('getPasien')
|
||||
->whereDate($column, $date)
|
||||
->orderBy($column)
|
||||
->orderBy('id')
|
||||
->get()
|
||||
->map(fn (Periksa $periksa) => $this->mapPeriksaToRegisterPayload($periksa))
|
||||
->values();
|
||||
|
||||
$payload = [
|
||||
'tanggal' => $date,
|
||||
'filter_column' => $column,
|
||||
'total' => $records->count(),
|
||||
'data' => $records,
|
||||
];
|
||||
|
||||
$jsonOptions = JSON_UNESCAPED_SLASHES;
|
||||
if ($this->option('pretty')) {
|
||||
$jsonOptions |= JSON_PRETTY_PRINT;
|
||||
}
|
||||
|
||||
$json = json_encode($payload, $jsonOptions);
|
||||
if ($json === false) {
|
||||
$this->error('Gagal membuat JSON.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if ($this->option('output')) {
|
||||
$output = (string) $this->option('output');
|
||||
File::ensureDirectoryExists(dirname($output));
|
||||
File::put($output, $json . PHP_EOL);
|
||||
$this->info('Export selesai: ' . $output);
|
||||
$this->info('Total data: ' . $records->count());
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$this->line($json);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function mapPeriksaToRegisterPayload(Periksa $periksa): array
|
||||
{
|
||||
$pasien = $periksa->getPasien;
|
||||
$tanggalLahir = $this->formatDate($periksa->tgllahirpasien ?? $pasien?->tgl_lahir);
|
||||
$tanggalRegis = $this->formatDate($periksa->tanggalregis) ?: $this->formatDate($periksa->daftar);
|
||||
$tanggalSampel = $this->formatDate($periksa->tanggalsampel) ?: $tanggalRegis;
|
||||
$diagnosa = $periksa->klinis ?: null;
|
||||
$klinisi = $periksa->klinisi ?: null;
|
||||
|
||||
return [
|
||||
'norm' => $periksa->noregister,
|
||||
'nama' => $periksa->nmpasien,
|
||||
'alamat' => $periksa->alamatpasien ?: config('global.addressapps'),
|
||||
'kota' => $pasien?->kota ?: config('global.subdomainapps'),
|
||||
'tgllahir' => $tanggalLahir ?: date('Y-m-d'),
|
||||
'nohap' => $periksa->tlppasien ?: '0000000000',
|
||||
'kelamin' => $periksa->jkpasien ?: 'L',
|
||||
'urgensi' => $periksa->urgensi ?: 'Elective',
|
||||
'beratbadan' => (string) ($periksa->berat ?? '0'),
|
||||
'nik' => $periksa->ktp ?: '0',
|
||||
'nobpjs' => $periksa->bpjs ?: '0',
|
||||
'drpeminta' => $klinisi,
|
||||
'asalpasien' => $periksa->asalpasien,
|
||||
'diagnosa' => $diagnosa,
|
||||
'statusbayar' => null,
|
||||
'nomor_lab' => $periksa->orderid,
|
||||
'jenispembayaran' => $periksa->asuransi ?: 'UMM',
|
||||
'klinisi' => $klinisi,
|
||||
'jenispemeriksaan' => $periksa->reques,
|
||||
'spesimen' => $periksa->nm_spesimen,
|
||||
'kodespesimen' => $periksa->kd_spesimen,
|
||||
'pengambilan' => $periksa->pengambilan,
|
||||
'keteranganrujukan' => $periksa->nmrs ?: 'RSSA Malang',
|
||||
'namarspengirim' => $periksa->nmrs ?: 'RSSA Malang',
|
||||
'tanggalregis' => $tanggalRegis ?: date('Y-m-d'),
|
||||
'tanggalsampel' => $tanggalSampel ?: date('Y-m-d'),
|
||||
'asalpengirim' => $periksa->asalpengirim,
|
||||
];
|
||||
}
|
||||
|
||||
private function isValidDate(string $date): bool
|
||||
{
|
||||
try {
|
||||
$parsed = Carbon::createFromFormat('Y-m-d', $date);
|
||||
} catch (\Throwable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $parsed && $parsed->format('Y-m-d') === $date;
|
||||
}
|
||||
|
||||
private function formatDate(mixed $value): ?string
|
||||
{
|
||||
if (!$value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return Carbon::parse($value)->format('Y-m-d');
|
||||
} catch (\Throwable) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user