Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
1.59% |
1 / 63 |
|
11.11% |
1 / 9 |
CRAP | |
0.00% |
0 / 1 |
MessageBackupToCloud | |
1.59% |
1 / 63 |
|
11.11% |
1 / 9 |
174.08 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
handle | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
6 | |||
createPathIfNotExists | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
writeDataToFile | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
deleteTmpFile | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
shouldSkipBackup | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
performLocalBackups | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
performCloudBackups | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
deleteOlderBackups | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace App\Console\Commands\Messages\Backups; |
4 | |
5 | use Illuminate\Console\Command; |
6 | use Illuminate\Support\Carbon; |
7 | use Illuminate\Support\Facades\DB; |
8 | use Illuminate\Support\Facades\Log; |
9 | use Google\Cloud\Storage\StorageClient; |
10 | |
11 | class MessageBackupToCloud extends Command |
12 | { |
13 | /** |
14 | * The name and signature of the console command. |
15 | * |
16 | * @var string |
17 | */ |
18 | protected $signature = 'db:messages-backup-to-cloud'; |
19 | |
20 | /** |
21 | * The console command description. |
22 | * |
23 | * @var string |
24 | */ |
25 | protected $description = 'Command to get all the messages from db and create a backup to cloud'; |
26 | |
27 | /** |
28 | * Create a new command instance. |
29 | * |
30 | * @return void |
31 | */ |
32 | public function __construct() |
33 | { |
34 | parent::__construct(); |
35 | } |
36 | |
37 | /** |
38 | * Execute the console command. |
39 | * |
40 | * @return int |
41 | */ |
42 | public function handle(): int |
43 | { |
44 | // Create cloud connection |
45 | $storage = new StorageClient([ |
46 | 'keyFilePath' => base_path() . "/gc-" . env('GC_APP_ENV') . ".json" |
47 | ]); |
48 | |
49 | // Create bucket instance |
50 | $bucket = $storage->bucket(env('GC_APP_ENV') . "-backups-bd"); |
51 | |
52 | // Get data from DB |
53 | $data = DB::table('messages')->get(); |
54 | |
55 | // Define the local path |
56 | $path = base_path() . env('GC_HOST_PATH'); |
57 | $this->createPathIfNotExists($path); |
58 | |
59 | // Write data to a local file and get the file size |
60 | $localFileSize = $this->writeDataToFile($data, $path); |
61 | |
62 | // Delete tmp file |
63 | $this->deleteTmpFile($path); |
64 | |
65 | // Check if backup can be skipped |
66 | if ($this->shouldSkipBackup($localFileSize, $bucket)) |
67 | { |
68 | // Log no need message |
69 | Log::channel('messages-backups') |
70 | ->info("No need to do db backup as there's no new messages!"); |
71 | |
72 | // I/O |
73 | $this->info("No need to do db backup as there's no new messages.."); |
74 | return 0; |
75 | } |
76 | |
77 | // Perform local backups |
78 | $this->performLocalBackups($data, $path); |
79 | |
80 | // Perform cloud backups |
81 | $timeAux = date("Y_m_d_H_i_s"); |
82 | $this->performCloudBackups($path, $timeAux); |
83 | |
84 | // Delete older backups in the cloud |
85 | $this->deleteOlderBackups($bucket); |
86 | |
87 | // Backup path cleanup to keep the limit to 5 files + 1 ( latest ) |
88 | exec("cd $path && ls -t | tail -n +7 | xargs -I {} rm {}"); |
89 | |
90 | // Log success |
91 | Log::channel('messages-backups') |
92 | ->info('Backups done with success!'); |
93 | |
94 | // I/O |
95 | $this->info("Messages backup to cloud done with success."); |
96 | |
97 | // Exit |
98 | return 0; |
99 | } |
100 | |
101 | /** |
102 | * Create a directory if it does not exist. |
103 | * |
104 | * @param string $path |
105 | * @return void |
106 | */ |
107 | public function createPathIfNotExists(string $path): void |
108 | { |
109 | if (!file_exists($path)) { |
110 | mkdir($path, 775, true); |
111 | } |
112 | } |
113 | |
114 | /** |
115 | * Write data to a temporary file and return the file size. |
116 | * |
117 | * @param mixed $data |
118 | * @param string $path |
119 | * @return int |
120 | */ |
121 | public function writeDataToFile(mixed $data, string $path): int |
122 | { |
123 | $tmp_file = $path . '/tmp_file.json'; |
124 | file_put_contents($tmp_file, json_encode($data), FILE_APPEND); |
125 | // unlink($tmp_file); |
126 | return filesize($tmp_file); |
127 | } |
128 | |
129 | public function deleteTmpFile(string $path): void |
130 | { |
131 | $tmp_file = $path . '/tmp_file.json'; |
132 | unlink($tmp_file); |
133 | } |
134 | |
135 | /** |
136 | * Check if the backup can be skipped based on file sizes. |
137 | * |
138 | * @param int $localFileSize |
139 | * @param mixed $bucket |
140 | * @return bool |
141 | */ |
142 | public function shouldSkipBackup(int $localFileSize, mixed $bucket): bool |
143 | { |
144 | $objectName = env('GC_CLOUD_PATH') . env('GC_CLOUD_FILE'); |
145 | $latestBackupObject = $bucket->object($objectName); |
146 | |
147 | // Check if the object exists |
148 | if ($latestBackupObject->exists()) |
149 | { |
150 | // Retrieve the size of the latest backup object |
151 | $latestBackupFileSize = $latestBackupObject->info()['size']; |
152 | } |
153 | else |
154 | { |
155 | // If the object doesn't exist, consider it as having size 0 |
156 | $latestBackupFileSize = 0; |
157 | } |
158 | |
159 | // Compare sizes |
160 | return $localFileSize <= $latestBackupFileSize; |
161 | } |
162 | |
163 | /** |
164 | * Perform local backups. |
165 | * |
166 | * @param mixed $data |
167 | * @param string $path |
168 | * @return void |
169 | */ |
170 | public function performLocalBackups(mixed $data, string $path): void |
171 | { |
172 | $file = $path . env('GC_HOST_FILE'); |
173 | $timeAux = date("Y_m_d_H_i_s"); |
174 | $fileLog = "$path/messages-backup-" . $timeAux . ".json"; |
175 | |
176 | file_put_contents($file, json_encode($data)); |
177 | file_put_contents($fileLog, json_encode($data)); |
178 | } |
179 | |
180 | /** |
181 | * Perform cloud backups. |
182 | * |
183 | * @param string $path |
184 | * @param string $timeAux |
185 | * @return void |
186 | */ |
187 | private function performCloudBackups(string $path, string $timeAux): void |
188 | { |
189 | // Create a new storage client for cloud backups |
190 | $storage = new StorageClient([ |
191 | 'keyFilePath' => base_path() . "/gc-" . env('GC_APP_ENV') . ".json" |
192 | ]); |
193 | |
194 | // Create a bucket instance for cloud backups |
195 | $bucket = $storage->bucket(env('GC_APP_ENV') . "-backups-bd"); |
196 | |
197 | // Upload the latest backup to the cloud |
198 | $bucket->upload(fopen($path . env('GC_CLOUD_FILE'), 'r'), [ |
199 | 'name' => env('GC_CLOUD_PATH') . env('GC_CLOUD_FILE'), |
200 | ]); |
201 | |
202 | // Upload a backup by hour to the cloud |
203 | $bucket->upload(fopen($path . "/messages-backup-" . $timeAux . ".json", 'r'), [ |
204 | 'name' => env('GC_CLOUD_PATH') . "messages-backup-" . $timeAux . ".json", |
205 | ]); |
206 | } |
207 | |
208 | /** |
209 | * Delete older backups from the cloud bucket. |
210 | * |
211 | * @param mixed $bucket |
212 | * @return void |
213 | */ |
214 | private function deleteOlderBackups(mixed $bucket): void |
215 | { |
216 | //$dayBeforeYesterday = Carbon::now()->subDays(2); |
217 | //$dayBeforeYesterdayBackupPrefix = 'messages-backup-' . $dayBeforeYesterday->format('Y_m_d_H'); |
218 | |
219 | // Get the current time minus 5 hours |
220 | $hours = Carbon::now()->subHour(5); |
221 | $dayBeforeYesterdayBackupPrefix = 'messages-backup-' . $hours->format('Y_m_d_H'); |
222 | |
223 | // List all objects in the bucket |
224 | $objects = $bucket->objects(); |
225 | $objectsArray = iterator_to_array($objects); |
226 | |
227 | // Filter objects based on the name prefix of the previous day's backups |
228 | $oldBackups = array_filter($objectsArray, function ($object) use ($dayBeforeYesterdayBackupPrefix) { |
229 | return str_contains($object->name(), $dayBeforeYesterdayBackupPrefix); |
230 | }); |
231 | |
232 | // Delete all older backups |
233 | foreach ($oldBackups as $oldBackup) { |
234 | $oldBackup->delete(); |
235 | Log::channel('messages-backups') |
236 | ->info('Deleted old backup ' . $oldBackup->name()); |
237 | } |
238 | } |
239 | } |