@arela/uploader 0.2.4 → 0.2.6
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.
- package/package.json +3 -2
- package/src/commands/UploadCommand.js +446 -0
- package/src/config/config.js +178 -0
- package/src/errors/ErrorHandler.js +278 -0
- package/src/errors/ErrorTypes.js +104 -0
- package/src/index-old.js +2658 -0
- package/src/index.js +302 -2573
- package/src/services/DatabaseService.js +1140 -0
- package/src/services/LoggingService.js +194 -0
- package/src/services/upload/ApiUploadService.js +153 -0
- package/src/services/upload/BaseUploadService.js +36 -0
- package/src/services/upload/SupabaseUploadService.js +126 -0
- package/src/services/upload/UploadServiceFactory.js +76 -0
- package/src/utils/FileOperations.js +148 -0
- package/src/utils/FileSanitizer.js +99 -0
- package/src/utils/PathDetector.js +198 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* File Operations Utility
|
|
6
|
+
* Provides optimized file system operations with batching
|
|
7
|
+
*/
|
|
8
|
+
export class FileOperations {
|
|
9
|
+
/**
|
|
10
|
+
* Batch read file stats for multiple files
|
|
11
|
+
* @param {string[]} filePaths - Array of file paths
|
|
12
|
+
* @returns {Array} Array of results with path, stats, and error
|
|
13
|
+
*/
|
|
14
|
+
static batchReadFileStats(filePaths) {
|
|
15
|
+
const results = [];
|
|
16
|
+
|
|
17
|
+
for (const filePath of filePaths) {
|
|
18
|
+
try {
|
|
19
|
+
const stats = fs.statSync(filePath);
|
|
20
|
+
results.push({ path: filePath, stats, error: null });
|
|
21
|
+
} catch (error) {
|
|
22
|
+
results.push({ path: filePath, stats: null, error: error.message });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return results;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if file exists synchronously
|
|
31
|
+
* @param {string} filePath - Path to file
|
|
32
|
+
* @returns {boolean} True if file exists
|
|
33
|
+
*/
|
|
34
|
+
static fileExists(filePath) {
|
|
35
|
+
try {
|
|
36
|
+
return fs.existsSync(filePath);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get file size safely
|
|
44
|
+
* @param {string} filePath - Path to file
|
|
45
|
+
* @returns {number|null} File size in bytes or null if error
|
|
46
|
+
*/
|
|
47
|
+
static getFileSize(filePath) {
|
|
48
|
+
try {
|
|
49
|
+
const stats = fs.statSync(filePath);
|
|
50
|
+
return stats.size;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get file modification time
|
|
58
|
+
* @param {string} filePath - Path to file
|
|
59
|
+
* @returns {Date|null} Modification time or null if error
|
|
60
|
+
*/
|
|
61
|
+
static getFileModTime(filePath) {
|
|
62
|
+
try {
|
|
63
|
+
const stats = fs.statSync(filePath);
|
|
64
|
+
return stats.mtime;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get file stats
|
|
72
|
+
* @param {string} filePath - Path to file
|
|
73
|
+
* @returns {Object|null} File stats or null if error
|
|
74
|
+
*/
|
|
75
|
+
static getFileStats(filePath) {
|
|
76
|
+
try {
|
|
77
|
+
return fs.statSync(filePath);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Create directory recursively if it doesn't exist
|
|
85
|
+
* @param {string} dirPath - Directory path
|
|
86
|
+
* @returns {boolean} True if successful
|
|
87
|
+
*/
|
|
88
|
+
static ensureDirectory(dirPath) {
|
|
89
|
+
try {
|
|
90
|
+
if (!fs.existsSync(dirPath)) {
|
|
91
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Read file with error handling
|
|
101
|
+
* @param {string} filePath - Path to file
|
|
102
|
+
* @param {string} encoding - File encoding (default: 'utf-8')
|
|
103
|
+
* @returns {string|null} File content or null if error
|
|
104
|
+
*/
|
|
105
|
+
static readFileContent(filePath, encoding = 'utf-8') {
|
|
106
|
+
try {
|
|
107
|
+
return fs.readFileSync(filePath, encoding);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Write file with error handling and directory creation
|
|
115
|
+
* @param {string} filePath - Path to file
|
|
116
|
+
* @param {string} content - Content to write
|
|
117
|
+
* @param {string} encoding - File encoding (default: 'utf-8')
|
|
118
|
+
* @returns {boolean} True if successful
|
|
119
|
+
*/
|
|
120
|
+
static writeFileContent(filePath, content, encoding = 'utf-8') {
|
|
121
|
+
try {
|
|
122
|
+
const dir = path.dirname(filePath);
|
|
123
|
+
this.ensureDirectory(dir);
|
|
124
|
+
fs.writeFileSync(filePath, content, encoding);
|
|
125
|
+
return true;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Append to file with error handling
|
|
133
|
+
* @param {string} filePath - Path to file
|
|
134
|
+
* @param {string} content - Content to append
|
|
135
|
+
* @param {string} encoding - File encoding (default: 'utf-8')
|
|
136
|
+
* @returns {boolean} True if successful
|
|
137
|
+
*/
|
|
138
|
+
static appendToFile(filePath, content, encoding = 'utf-8') {
|
|
139
|
+
try {
|
|
140
|
+
fs.appendFileSync(filePath, content, encoding);
|
|
141
|
+
return true;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default FileOperations;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* File Sanitization Utility
|
|
5
|
+
* Handles filename sanitization with caching for performance
|
|
6
|
+
*/
|
|
7
|
+
export class FileSanitizer {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.cache = new Map();
|
|
10
|
+
this.patterns = [
|
|
11
|
+
[/[áàâäãåāăą]/gi, 'a'],
|
|
12
|
+
[/[éèêëēĕėę]/gi, 'e'],
|
|
13
|
+
[/[íìîïīĭį]/gi, 'i'],
|
|
14
|
+
[/[óòôöõōŏő]/gi, 'o'],
|
|
15
|
+
[/[úùûüūŭů]/gi, 'u'],
|
|
16
|
+
[/[ñň]/gi, 'n'],
|
|
17
|
+
[/[ç]/gi, 'c'],
|
|
18
|
+
[/[ý]/gi, 'y'],
|
|
19
|
+
[/[멕]/g, 'meok'],
|
|
20
|
+
[/[시]/g, 'si'],
|
|
21
|
+
[/[코]/g, 'ko'],
|
|
22
|
+
[/[용]/g, 'yong'],
|
|
23
|
+
[/[가-힣]/g, 'kr'],
|
|
24
|
+
[/[\u0300-\u036f]/g, ''],
|
|
25
|
+
[/[\\?%*:|"<>[\]~`^]/g, '-'],
|
|
26
|
+
[/[{}]/g, '-'],
|
|
27
|
+
[/[&]/g, 'and'],
|
|
28
|
+
[/[()]/g, ''],
|
|
29
|
+
[/\s+/g, '-'],
|
|
30
|
+
[/-+/g, '-'],
|
|
31
|
+
[/^-+|-+$/g, ''],
|
|
32
|
+
[/^\.+/, ''],
|
|
33
|
+
[/[^\w.-]/g, ''],
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Sanitize a filename for safe storage
|
|
39
|
+
* @param {string} fileName - Original filename
|
|
40
|
+
* @returns {string} Sanitized filename
|
|
41
|
+
*/
|
|
42
|
+
sanitizeFileName(fileName) {
|
|
43
|
+
if (this.cache.has(fileName)) {
|
|
44
|
+
return this.cache.get(fileName);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const ext = path.extname(fileName);
|
|
48
|
+
const nameWithoutExt = path.basename(fileName, ext);
|
|
49
|
+
|
|
50
|
+
// If already clean, return as-is
|
|
51
|
+
if (/^[a-zA-Z0-9._-]+$/.test(nameWithoutExt)) {
|
|
52
|
+
const result = fileName;
|
|
53
|
+
this.cache.set(fileName, result);
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let sanitized = nameWithoutExt.normalize('NFD');
|
|
58
|
+
|
|
59
|
+
// Apply all sanitization patterns
|
|
60
|
+
for (const [pattern, replacement] of this.patterns) {
|
|
61
|
+
sanitized = sanitized.replace(pattern, replacement);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Additional cleanup
|
|
65
|
+
sanitized = sanitized
|
|
66
|
+
.replace(/~/g, '-')
|
|
67
|
+
.replace(/\s+/g, '-')
|
|
68
|
+
.replace(/\.+/g, '-')
|
|
69
|
+
.replace(/-+/g, '-')
|
|
70
|
+
.replace(/^-+|-+$/g, '');
|
|
71
|
+
|
|
72
|
+
if (!sanitized) {
|
|
73
|
+
sanitized = 'unnamed_file';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const result = sanitized + ext;
|
|
77
|
+
this.cache.set(fileName, result);
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Clear the sanitization cache
|
|
83
|
+
*/
|
|
84
|
+
clearCache() {
|
|
85
|
+
this.cache.clear();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get cache size for monitoring
|
|
90
|
+
* @returns {number} Number of cached entries
|
|
91
|
+
*/
|
|
92
|
+
getCacheSize() {
|
|
93
|
+
return this.cache.size;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Export singleton instance
|
|
98
|
+
export const fileSanitizer = new FileSanitizer();
|
|
99
|
+
export default fileSanitizer;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Path Detection Utility
|
|
5
|
+
* Extracts year and pedimento information from file paths with caching
|
|
6
|
+
*/
|
|
7
|
+
export class PathDetector {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.cache = new Map();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Extract year and pedimento number from file path with caching
|
|
14
|
+
* @param {string} filePath - Full file path
|
|
15
|
+
* @param {string} basePath - Base path to make relative
|
|
16
|
+
* @returns {Object} Detection result with year, pedimento, and detected flag
|
|
17
|
+
*/
|
|
18
|
+
extractYearAndPedimentoFromPath(filePath, basePath) {
|
|
19
|
+
const cacheKey = `${filePath}|${basePath}`;
|
|
20
|
+
|
|
21
|
+
if (this.cache.has(cacheKey)) {
|
|
22
|
+
return this.cache.get(cacheKey);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const detection = this.#performDetection(filePath, basePath);
|
|
26
|
+
this.cache.set(cacheKey, detection);
|
|
27
|
+
|
|
28
|
+
return detection;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Perform the actual path detection
|
|
33
|
+
* @private
|
|
34
|
+
* @param {string} filePath - Full file path
|
|
35
|
+
* @param {string} basePath - Base path to make relative
|
|
36
|
+
* @returns {Object} Detection result
|
|
37
|
+
*/
|
|
38
|
+
#performDetection(filePath, basePath) {
|
|
39
|
+
try {
|
|
40
|
+
const relativePath = path.relative(basePath, filePath);
|
|
41
|
+
const pathParts = relativePath.split(path.sep);
|
|
42
|
+
|
|
43
|
+
let year = null;
|
|
44
|
+
let pedimento = null;
|
|
45
|
+
|
|
46
|
+
// Pattern 1: Direct year/pedimento structure (2024/4023260)
|
|
47
|
+
year = this.#detectDirectPattern(pathParts);
|
|
48
|
+
pedimento = year ? this.#findPedimentoForYear(pathParts, year) : null;
|
|
49
|
+
|
|
50
|
+
// Pattern 2: Named patterns (año2024, ped4023260)
|
|
51
|
+
if (!year || !pedimento) {
|
|
52
|
+
const namedDetection = this.#detectNamedPatterns(pathParts);
|
|
53
|
+
year = year || namedDetection.year;
|
|
54
|
+
pedimento = pedimento || namedDetection.pedimento;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Pattern 3: Loose year detection
|
|
58
|
+
if (!year) {
|
|
59
|
+
year = this.#detectLooseYear(pathParts);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Pattern 4: Loose pedimento detection
|
|
63
|
+
if (!pedimento) {
|
|
64
|
+
pedimento = this.#detectLoosePedimento(pathParts);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
year,
|
|
69
|
+
pedimento,
|
|
70
|
+
detected: !!(year && pedimento),
|
|
71
|
+
};
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return {
|
|
74
|
+
year: null,
|
|
75
|
+
pedimento: null,
|
|
76
|
+
detected: false,
|
|
77
|
+
error: error.message,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Detect direct year/pedimento pattern
|
|
84
|
+
* @private
|
|
85
|
+
* @param {string[]} pathParts - Path components
|
|
86
|
+
* @returns {string|null} Detected year
|
|
87
|
+
*/
|
|
88
|
+
#detectDirectPattern(pathParts) {
|
|
89
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
90
|
+
const yearMatch = pathParts[i].match(/^(202[0-9])$/);
|
|
91
|
+
if (yearMatch) {
|
|
92
|
+
return yearMatch[1];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Find pedimento for a detected year
|
|
100
|
+
* @private
|
|
101
|
+
* @param {string[]} pathParts - Path components
|
|
102
|
+
* @param {string} year - Detected year
|
|
103
|
+
* @returns {string|null} Detected pedimento
|
|
104
|
+
*/
|
|
105
|
+
#findPedimentoForYear(pathParts, year) {
|
|
106
|
+
const yearIndex = pathParts.findIndex((part) => part === year);
|
|
107
|
+
if (yearIndex >= 0 && yearIndex < pathParts.length - 1) {
|
|
108
|
+
const nextPart = pathParts[yearIndex + 1];
|
|
109
|
+
const pedimentoMatch = nextPart.match(/^(\d{4,8})$/);
|
|
110
|
+
if (pedimentoMatch) {
|
|
111
|
+
return pedimentoMatch[1];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Detect named patterns (año2024, ped4023260)
|
|
119
|
+
* @private
|
|
120
|
+
* @param {string[]} pathParts - Path components
|
|
121
|
+
* @returns {Object} Detection result with year and pedimento
|
|
122
|
+
*/
|
|
123
|
+
#detectNamedPatterns(pathParts) {
|
|
124
|
+
let year = null;
|
|
125
|
+
let pedimento = null;
|
|
126
|
+
|
|
127
|
+
for (const part of pathParts) {
|
|
128
|
+
if (!year) {
|
|
129
|
+
const namedYearMatch = part.match(/(?:año|year|anio)(\d{4})/i);
|
|
130
|
+
if (namedYearMatch) {
|
|
131
|
+
year = namedYearMatch[1];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!pedimento) {
|
|
136
|
+
const namedPedimentoMatch = part.match(
|
|
137
|
+
/(?:ped|pedimento|pedi)(\d{4,8})/i,
|
|
138
|
+
);
|
|
139
|
+
if (namedPedimentoMatch) {
|
|
140
|
+
pedimento = namedPedimentoMatch[1];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { year, pedimento };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Detect loose year pattern
|
|
150
|
+
* @private
|
|
151
|
+
* @param {string[]} pathParts - Path components
|
|
152
|
+
* @returns {string|null} Detected year
|
|
153
|
+
*/
|
|
154
|
+
#detectLooseYear(pathParts) {
|
|
155
|
+
for (const part of pathParts) {
|
|
156
|
+
const yearMatch = part.match(/(202[0-9])/);
|
|
157
|
+
if (yearMatch) {
|
|
158
|
+
return yearMatch[1];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Detect loose pedimento pattern
|
|
166
|
+
* @private
|
|
167
|
+
* @param {string[]} pathParts - Path components
|
|
168
|
+
* @returns {string|null} Detected pedimento
|
|
169
|
+
*/
|
|
170
|
+
#detectLoosePedimento(pathParts) {
|
|
171
|
+
for (const part of pathParts) {
|
|
172
|
+
const pedimentoMatch = part.match(/(\d{4,8})/);
|
|
173
|
+
if (pedimentoMatch && pedimentoMatch[1].length >= 4) {
|
|
174
|
+
return pedimentoMatch[1];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Clear the detection cache
|
|
182
|
+
*/
|
|
183
|
+
clearCache() {
|
|
184
|
+
this.cache.clear();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get cache size for monitoring
|
|
189
|
+
* @returns {number} Number of cached entries
|
|
190
|
+
*/
|
|
191
|
+
getCacheSize() {
|
|
192
|
+
return this.cache.size;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Export singleton instance
|
|
197
|
+
export const pathDetector = new PathDetector();
|
|
198
|
+
export default pathDetector;
|