@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 the CleanupExpiredFiles artisan command.
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 the CleanupExpiredFiles artisan command.
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 CleanupExpiredFiles extends Command
53
+ class CleanupFiles extends Command
28
54
  {
29
- /**
30
- * The name and signature of the console command.
31
- */
32
- protected $signature = 'omnify:cleanup-expired-files
33
- {--dry-run : Show what would be deleted without actually deleting}';
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
- * The console command description.
83
+ * Phase 1: Soft-delete expired temporary file records.
84
+ * Physical files remain on disk for the grace period.
37
85
  */
38
- protected $description = 'Delete expired temporary files';
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
- * Execute the console command.
108
+ * Phase 2: Force-delete soft-deleted records past grace period
109
+ * and remove physical files from storage.
42
110
  */
43
- public function handle(): int
111
+ private function phase2Purge(bool $dryRun): int
44
112
  {
45
- $query = File::expired();
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('No expired files found.');
120
+ $this->info("[Phase 2] No soft-deleted files older than {$hours}h.");
50
121
  return self::SUCCESS;
51
122
  }
52
123
 
53
- if ($this->option('dry-run')) {
54
- $this->info("Would delete {$count} expired file(s).");
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
- $deleted = 0;
59
- $query->chunkById(100, function ($files) use (&$deleted) {
129
+ $purged = 0;
130
+ $errors = 0;
131
+
132
+ $query->chunkById(100, function ($files) use (&$purged, &$errors) {
60
133
  foreach ($files as $file) {
61
- // Delete from storage
62
- if ($file->path && Storage::disk($file->disk)->exists($file->path)) {
63
- Storage::disk($file->disk)->delete($file->path);
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("Deleted {$deleted} expired file(s).");
72
- return self::SUCCESS;
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. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnifyjp/ts",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "TypeScript model type generator from Omnify schemas.json",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",