@omnifyjp/ts 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generates
|
|
2
|
+
* Generates a single file cleanup command with two phases:
|
|
3
|
+
* 1. Soft-delete expired temp records (keeps physical files)
|
|
4
|
+
* 2. Force-delete soft-deleted records past grace period + remove physical files
|
|
5
|
+
*
|
|
3
6
|
* Only generated when fileConfig.tempFlow is enabled.
|
|
4
7
|
*/
|
|
5
8
|
import { SchemaReader } from './schema-reader.js';
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generates
|
|
2
|
+
* Generates a single file cleanup command with two phases:
|
|
3
|
+
* 1. Soft-delete expired temp records (keeps physical files)
|
|
4
|
+
* 2. Force-delete soft-deleted records past grace period + remove physical files
|
|
5
|
+
*
|
|
3
6
|
* Only generated when fileConfig.tempFlow is enabled.
|
|
4
7
|
*/
|
|
5
8
|
import { baseFile } from './types.js';
|
|
@@ -9,11 +12,34 @@ export function generateFileCleanup(reader, config) {
|
|
|
9
12
|
if (!fileConfig?.tempFlow)
|
|
10
13
|
return [];
|
|
11
14
|
const modelNamespace = config.models.namespace;
|
|
15
|
+
const purgeHours = parseDurationHours(fileConfig.purgeAfter ?? '72h');
|
|
16
|
+
return [generateCommand(modelNamespace, purgeHours)];
|
|
17
|
+
}
|
|
18
|
+
/** Parse a duration string like "72h" or "3d" to hours. */
|
|
19
|
+
function parseDurationHours(duration) {
|
|
20
|
+
const match = duration.match(/^(\d+)(h|d)$/);
|
|
21
|
+
if (!match)
|
|
22
|
+
return 72;
|
|
23
|
+
const value = parseInt(match[1], 10);
|
|
24
|
+
const unit = match[2];
|
|
25
|
+
return unit === 'd' ? value * 24 : value;
|
|
26
|
+
}
|
|
27
|
+
function generateCommand(modelNamespace, purgeHours) {
|
|
12
28
|
const content = `<?php
|
|
13
29
|
|
|
14
30
|
namespace App\\Console\\Commands;
|
|
15
31
|
|
|
16
32
|
/**
|
|
33
|
+
* Two-phase file cleanup:
|
|
34
|
+
* Phase 1: Soft-delete expired temp records (physical files stay on disk)
|
|
35
|
+
* Phase 2: Force-delete soft-deleted records past grace period + remove physical files
|
|
36
|
+
*
|
|
37
|
+
* Usage:
|
|
38
|
+
* php artisan omnify:cleanup-files # run both phases
|
|
39
|
+
* php artisan omnify:cleanup-files --phase=1 # soft-delete only
|
|
40
|
+
* php artisan omnify:cleanup-files --phase=2 # purge only
|
|
41
|
+
* php artisan omnify:cleanup-files --dry-run # preview without changes
|
|
42
|
+
*
|
|
17
43
|
* DO NOT EDIT - This file is auto-generated by Omnify.
|
|
18
44
|
* Any changes will be overwritten on next generation.
|
|
19
45
|
*
|
|
@@ -24,56 +50,108 @@ use Illuminate\\Console\\Command;
|
|
|
24
50
|
use Illuminate\\Support\\Facades\\Storage;
|
|
25
51
|
use ${modelNamespace}\\File;
|
|
26
52
|
|
|
27
|
-
class
|
|
53
|
+
class CleanupFiles extends Command
|
|
28
54
|
{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
55
|
+
protected $signature = 'omnify:cleanup-files
|
|
56
|
+
{--phase= : Run specific phase only (1=soft-delete, 2=purge)}
|
|
57
|
+
{--purge-hours=${purgeHours} : Grace period in hours before physical deletion}
|
|
58
|
+
{--dry-run : Preview what would happen without making changes}';
|
|
59
|
+
|
|
60
|
+
protected $description = 'Clean up expired and deleted file attachments';
|
|
61
|
+
|
|
62
|
+
public function handle(): int
|
|
63
|
+
{
|
|
64
|
+
$phase = $this->option('phase');
|
|
65
|
+
$dryRun = (bool) $this->option('dry-run');
|
|
66
|
+
|
|
67
|
+
$result = self::SUCCESS;
|
|
68
|
+
|
|
69
|
+
if ($phase === null || $phase === '1') {
|
|
70
|
+
$r = $this->phase1SoftDelete($dryRun);
|
|
71
|
+
if ($r !== self::SUCCESS) $result = $r;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if ($phase === null || $phase === '2') {
|
|
75
|
+
$r = $this->phase2Purge($dryRun);
|
|
76
|
+
if ($r !== self::SUCCESS) $result = $r;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return $result;
|
|
80
|
+
}
|
|
34
81
|
|
|
35
82
|
/**
|
|
36
|
-
*
|
|
83
|
+
* Phase 1: Soft-delete expired temporary file records.
|
|
84
|
+
* Physical files remain on disk for the grace period.
|
|
37
85
|
*/
|
|
38
|
-
|
|
86
|
+
private function phase1SoftDelete(bool $dryRun): int
|
|
87
|
+
{
|
|
88
|
+
$query = File::expired();
|
|
89
|
+
$count = $query->count();
|
|
90
|
+
|
|
91
|
+
if ($count === 0) {
|
|
92
|
+
$this->info('[Phase 1] No expired temp files.');
|
|
93
|
+
return self::SUCCESS;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if ($dryRun) {
|
|
97
|
+
$this->info("[Phase 1] Would soft-delete {$count} expired file(s).");
|
|
98
|
+
return self::SUCCESS;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
$deleted = $query->delete();
|
|
102
|
+
$this->info("[Phase 1] Soft-deleted {$deleted} expired file(s). Physical files retained.");
|
|
103
|
+
|
|
104
|
+
return self::SUCCESS;
|
|
105
|
+
}
|
|
39
106
|
|
|
40
107
|
/**
|
|
41
|
-
*
|
|
108
|
+
* Phase 2: Force-delete soft-deleted records past grace period
|
|
109
|
+
* and remove physical files from storage.
|
|
42
110
|
*/
|
|
43
|
-
|
|
111
|
+
private function phase2Purge(bool $dryRun): int
|
|
44
112
|
{
|
|
45
|
-
$
|
|
113
|
+
$hours = (int) $this->option('purge-hours');
|
|
114
|
+
$cutoff = now()->subHours($hours);
|
|
115
|
+
|
|
116
|
+
$query = File::onlyTrashed()->where('deleted_at', '<', $cutoff);
|
|
46
117
|
$count = $query->count();
|
|
47
118
|
|
|
48
119
|
if ($count === 0) {
|
|
49
|
-
$this->info(
|
|
120
|
+
$this->info("[Phase 2] No soft-deleted files older than {$hours}h.");
|
|
50
121
|
return self::SUCCESS;
|
|
51
122
|
}
|
|
52
123
|
|
|
53
|
-
if ($
|
|
54
|
-
$this->info("Would
|
|
124
|
+
if ($dryRun) {
|
|
125
|
+
$this->info("[Phase 2] Would purge {$count} file(s) older than {$hours}h.");
|
|
55
126
|
return self::SUCCESS;
|
|
56
127
|
}
|
|
57
128
|
|
|
58
|
-
$
|
|
59
|
-
$
|
|
129
|
+
$purged = 0;
|
|
130
|
+
$errors = 0;
|
|
131
|
+
|
|
132
|
+
$query->chunkById(100, function ($files) use (&$purged, &$errors) {
|
|
60
133
|
foreach ($files as $file) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
134
|
+
try {
|
|
135
|
+
if ($file->path && Storage::disk($file->disk)->exists($file->path)) {
|
|
136
|
+
Storage::disk($file->disk)->delete($file->path);
|
|
137
|
+
}
|
|
138
|
+
$file->forceDelete();
|
|
139
|
+
$purged++;
|
|
140
|
+
} catch (\\Throwable $e) {
|
|
141
|
+
$this->error("Failed to purge file {$file->id}: {$e->getMessage()}");
|
|
142
|
+
$errors++;
|
|
64
143
|
}
|
|
65
|
-
// Soft delete the record
|
|
66
|
-
$file->delete();
|
|
67
|
-
$deleted++;
|
|
68
144
|
}
|
|
69
145
|
});
|
|
70
146
|
|
|
71
|
-
$this->info("
|
|
72
|
-
|
|
147
|
+
$this->info("[Phase 2] Purged {$purged} file(s). Physical files removed.");
|
|
148
|
+
if ($errors > 0) {
|
|
149
|
+
$this->warn("{$errors} file(s) failed to purge.");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return $errors > 0 ? self::FAILURE : self::SUCCESS;
|
|
73
153
|
}
|
|
74
154
|
}
|
|
75
155
|
`;
|
|
76
|
-
return
|
|
77
|
-
baseFile('app/Console/Commands/CleanupExpiredFiles.php', content),
|
|
78
|
-
];
|
|
156
|
+
return baseFile('app/Console/Commands/CleanupFiles.php', content);
|
|
79
157
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export interface FileConfigExport {
|
|
|
10
10
|
readonly tempFlow?: boolean;
|
|
11
11
|
readonly tempTtl?: string;
|
|
12
12
|
readonly cleanupSchedule?: string;
|
|
13
|
+
readonly purgeAfter?: string;
|
|
14
|
+
readonly purgeSchedule?: string;
|
|
13
15
|
readonly defaultDisk?: string;
|
|
14
16
|
}
|
|
15
17
|
/** Top-level schemas.json structure. */
|