hazo_files 1.0.0
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/.cursor/rules/db_schema.mdc +0 -0
- package/.cursor/rules/design.mdc +0 -0
- package/.cursor/rules/general.mdc +0 -0
- package/CHANGE_LOG.md +341 -0
- package/CLAUDE.md +926 -0
- package/README.md +929 -0
- package/SETUP_CHECKLIST.md +931 -0
- package/TECHDOC.md +325 -0
- package/dist/index.d.mts +1031 -0
- package/dist/index.d.ts +1031 -0
- package/dist/index.js +2457 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2333 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ui/index.js +4054 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/index.mjs +3982 -0
- package/dist/ui/index.mjs.map +1 -0
- package/docs/ADDING_MODULES.md +964 -0
- package/hazo_files_config.ini +31 -0
- package/package.json +83 -0
|
@@ -0,0 +1,931 @@
|
|
|
1
|
+
# hazo_files Setup Checklist
|
|
2
|
+
|
|
3
|
+
Step-by-step guide to get hazo_files up and running in your project. Check off each step as you complete it.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- [ ] Node.js 16+ installed (`node --version`)
|
|
8
|
+
- [ ] npm or yarn package manager
|
|
9
|
+
- [ ] Text editor or IDE
|
|
10
|
+
- [ ] (Optional) React 18+ for UI components
|
|
11
|
+
- [ ] (Optional) Next.js 14+ for full-stack integration
|
|
12
|
+
- [ ] (Optional) PostgreSQL or SQLite for Google Drive token storage
|
|
13
|
+
|
|
14
|
+
## Part 1: Basic Installation
|
|
15
|
+
|
|
16
|
+
### 1.1 Install Package
|
|
17
|
+
|
|
18
|
+
- [ ] Install hazo_files package:
|
|
19
|
+
```bash
|
|
20
|
+
npm install hazo_files
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- [ ] Verify installation:
|
|
24
|
+
```bash
|
|
25
|
+
npm list hazo_files
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 1.2 Create Configuration File
|
|
29
|
+
|
|
30
|
+
- [ ] Create `hazo_files_config.ini` in your project root:
|
|
31
|
+
```bash
|
|
32
|
+
touch hazo_files_config.ini
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- [ ] Add basic configuration:
|
|
36
|
+
```ini
|
|
37
|
+
[general]
|
|
38
|
+
provider = local
|
|
39
|
+
|
|
40
|
+
[local]
|
|
41
|
+
base_path = ./files
|
|
42
|
+
allowed_extensions =
|
|
43
|
+
max_file_size = 0
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 1.3 Test Basic Setup
|
|
47
|
+
|
|
48
|
+
- [ ] Create a test script `test-hazo.js`:
|
|
49
|
+
```javascript
|
|
50
|
+
const { createInitializedFileManager } = require('hazo_files');
|
|
51
|
+
|
|
52
|
+
async function test() {
|
|
53
|
+
const fm = await createInitializedFileManager();
|
|
54
|
+
console.log('FileManager initialized!');
|
|
55
|
+
console.log('Provider:', fm.getProvider());
|
|
56
|
+
|
|
57
|
+
const result = await fm.createDirectory('/test');
|
|
58
|
+
console.log('Test directory created:', result.success);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
test().catch(console.error);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- [ ] Run the test:
|
|
65
|
+
```bash
|
|
66
|
+
node test-hazo.js
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- [ ] Verify `./files/test` directory was created
|
|
70
|
+
|
|
71
|
+
- [ ] Clean up test files:
|
|
72
|
+
```bash
|
|
73
|
+
rm test-hazo.js
|
|
74
|
+
rm -rf ./files/test
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Checkpoint**: Basic installation complete. FileManager can create directories.
|
|
78
|
+
|
|
79
|
+
## Part 2: Local Storage Setup
|
|
80
|
+
|
|
81
|
+
### 2.1 Configure Local Storage
|
|
82
|
+
|
|
83
|
+
- [ ] Update `hazo_files_config.ini`:
|
|
84
|
+
```ini
|
|
85
|
+
[general]
|
|
86
|
+
provider = local
|
|
87
|
+
|
|
88
|
+
[local]
|
|
89
|
+
base_path = ./storage
|
|
90
|
+
allowed_extensions = jpg,png,pdf,txt,doc,docx,xlsx
|
|
91
|
+
max_file_size = 10485760
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- [ ] Create storage directory:
|
|
95
|
+
```bash
|
|
96
|
+
mkdir -p ./storage
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- [ ] Set appropriate permissions (Unix/Linux):
|
|
100
|
+
```bash
|
|
101
|
+
chmod 750 ./storage
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 2.2 Implement File Operations
|
|
105
|
+
|
|
106
|
+
- [ ] Create `file-operations.js`:
|
|
107
|
+
```javascript
|
|
108
|
+
const { createInitializedFileManager } = require('hazo_files');
|
|
109
|
+
const fs = require('fs');
|
|
110
|
+
|
|
111
|
+
async function testOperations() {
|
|
112
|
+
const fm = await createInitializedFileManager();
|
|
113
|
+
|
|
114
|
+
// Create directory
|
|
115
|
+
await fm.createDirectory('/documents');
|
|
116
|
+
|
|
117
|
+
// Write text file
|
|
118
|
+
await fm.writeFile('/documents/readme.txt', 'Hello, World!');
|
|
119
|
+
|
|
120
|
+
// Upload file
|
|
121
|
+
const buffer = Buffer.from('Test content');
|
|
122
|
+
await fm.uploadFile(buffer, '/documents/test.txt');
|
|
123
|
+
|
|
124
|
+
// List directory
|
|
125
|
+
const result = await fm.listDirectory('/documents');
|
|
126
|
+
console.log('Files:', result.data);
|
|
127
|
+
|
|
128
|
+
// Download file
|
|
129
|
+
const readResult = await fm.readFile('/documents/readme.txt');
|
|
130
|
+
console.log('Content:', readResult.data);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
testOperations().catch(console.error);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- [ ] Run the operations test:
|
|
137
|
+
```bash
|
|
138
|
+
node file-operations.js
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- [ ] Verify files in `./storage/documents/`
|
|
142
|
+
|
|
143
|
+
**Checkpoint**: Local storage is fully functional.
|
|
144
|
+
|
|
145
|
+
## Part 3: Next.js Integration (Optional)
|
|
146
|
+
|
|
147
|
+
### 3.1 Setup Next.js Project
|
|
148
|
+
|
|
149
|
+
- [ ] If not already in a Next.js project, create one:
|
|
150
|
+
```bash
|
|
151
|
+
npx create-next-app@latest my-file-app
|
|
152
|
+
cd my-file-app
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- [ ] Install hazo_files in Next.js project:
|
|
156
|
+
```bash
|
|
157
|
+
npm install hazo_files
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- [ ] Copy `hazo_files_config.ini` to Next.js project root
|
|
161
|
+
|
|
162
|
+
### 3.2 Create API Routes
|
|
163
|
+
|
|
164
|
+
- [ ] Create `app/api/files/route.ts`:
|
|
165
|
+
```typescript
|
|
166
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
167
|
+
import { createInitializedFileManager } from 'hazo_files';
|
|
168
|
+
|
|
169
|
+
async function getFileManager() {
|
|
170
|
+
return createInitializedFileManager({
|
|
171
|
+
config: {
|
|
172
|
+
provider: 'local',
|
|
173
|
+
local: {
|
|
174
|
+
basePath: process.env.LOCAL_STORAGE_BASE_PATH || './files',
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function GET(request: NextRequest) {
|
|
181
|
+
const { searchParams } = new URL(request.url);
|
|
182
|
+
const action = searchParams.get('action');
|
|
183
|
+
const path = searchParams.get('path') || '/';
|
|
184
|
+
|
|
185
|
+
const fm = await getFileManager();
|
|
186
|
+
|
|
187
|
+
switch (action) {
|
|
188
|
+
case 'list':
|
|
189
|
+
return NextResponse.json(await fm.listDirectory(path));
|
|
190
|
+
case 'tree':
|
|
191
|
+
const depth = parseInt(searchParams.get('depth') || '3', 10);
|
|
192
|
+
return NextResponse.json(await fm.getFolderTree(path, depth));
|
|
193
|
+
default:
|
|
194
|
+
return NextResponse.json({ success: false, error: 'Invalid action' });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export async function POST(request: NextRequest) {
|
|
199
|
+
const body = await request.json();
|
|
200
|
+
const { action, ...params } = body;
|
|
201
|
+
|
|
202
|
+
const fm = await getFileManager();
|
|
203
|
+
|
|
204
|
+
switch (action) {
|
|
205
|
+
case 'createDirectory':
|
|
206
|
+
return NextResponse.json(await fm.createDirectory(params.path));
|
|
207
|
+
case 'deleteFile':
|
|
208
|
+
return NextResponse.json(await fm.deleteFile(params.path));
|
|
209
|
+
case 'renameFile':
|
|
210
|
+
return NextResponse.json(await fm.renameFile(params.path, params.newName));
|
|
211
|
+
default:
|
|
212
|
+
return NextResponse.json({ success: false, error: 'Invalid action' });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
- [ ] Create `app/api/files/upload/route.ts`:
|
|
218
|
+
```typescript
|
|
219
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
220
|
+
import { createInitializedFileManager } from 'hazo_files';
|
|
221
|
+
|
|
222
|
+
async function getFileManager() {
|
|
223
|
+
return createInitializedFileManager({
|
|
224
|
+
config: {
|
|
225
|
+
provider: 'local',
|
|
226
|
+
local: {
|
|
227
|
+
basePath: process.env.LOCAL_STORAGE_BASE_PATH || './files',
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function POST(request: NextRequest) {
|
|
234
|
+
const formData = await request.formData();
|
|
235
|
+
const file = formData.get('file') as File;
|
|
236
|
+
const path = formData.get('path') as string;
|
|
237
|
+
|
|
238
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
239
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
240
|
+
|
|
241
|
+
const fm = await getFileManager();
|
|
242
|
+
return NextResponse.json(await fm.uploadFile(buffer, path));
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
- [ ] Create `app/api/files/download/route.ts`:
|
|
247
|
+
```typescript
|
|
248
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
249
|
+
import { createInitializedFileManager } from 'hazo_files';
|
|
250
|
+
|
|
251
|
+
async function getFileManager() {
|
|
252
|
+
return createInitializedFileManager({
|
|
253
|
+
config: {
|
|
254
|
+
provider: 'local',
|
|
255
|
+
local: {
|
|
256
|
+
basePath: process.env.LOCAL_STORAGE_BASE_PATH || './files',
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export async function GET(request: NextRequest) {
|
|
263
|
+
const { searchParams } = new URL(request.url);
|
|
264
|
+
const path = searchParams.get('path') || '/';
|
|
265
|
+
|
|
266
|
+
const fm = await getFileManager();
|
|
267
|
+
const result = await fm.downloadFile(path);
|
|
268
|
+
|
|
269
|
+
if (!result.success) {
|
|
270
|
+
return NextResponse.json({ success: false, error: result.error }, { status: 404 });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const buffer = result.data as Buffer;
|
|
274
|
+
return new NextResponse(buffer, {
|
|
275
|
+
headers: {
|
|
276
|
+
'Content-Type': 'application/octet-stream',
|
|
277
|
+
'Content-Disposition': `attachment; filename="${path.split('/').pop()}"`,
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### 3.3 Test API Routes
|
|
284
|
+
|
|
285
|
+
- [ ] Start Next.js dev server:
|
|
286
|
+
```bash
|
|
287
|
+
npm run dev
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
- [ ] Test list endpoint:
|
|
291
|
+
```bash
|
|
292
|
+
curl http://localhost:3000/api/files?action=list&path=/
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
- [ ] Test create directory:
|
|
296
|
+
```bash
|
|
297
|
+
curl -X POST http://localhost:3000/api/files \
|
|
298
|
+
-H "Content-Type: application/json" \
|
|
299
|
+
-d '{"action":"createDirectory","path":"/test-api"}'
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
- [ ] Verify directory created in `./files/test-api`
|
|
303
|
+
|
|
304
|
+
**Checkpoint**: API routes are working correctly.
|
|
305
|
+
|
|
306
|
+
## Part 4: UI Components (Optional)
|
|
307
|
+
|
|
308
|
+
### 4.1 Install React Dependencies
|
|
309
|
+
|
|
310
|
+
- [ ] Install React (if not already installed):
|
|
311
|
+
```bash
|
|
312
|
+
npm install react react-dom
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
- [ ] Install UI dependencies (for styling):
|
|
316
|
+
```bash
|
|
317
|
+
npm install tailwindcss @tailwindcss/forms
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
- [ ] Install drag-and-drop dependencies (for NamingRuleConfigurator):
|
|
321
|
+
```bash
|
|
322
|
+
npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### 4.2 Create API Adapter
|
|
326
|
+
|
|
327
|
+
- [ ] Create `lib/file-api.ts`:
|
|
328
|
+
```typescript
|
|
329
|
+
import type { FileBrowserAPI } from 'hazo_files/ui';
|
|
330
|
+
|
|
331
|
+
export function createFileAPI(): FileBrowserAPI {
|
|
332
|
+
return {
|
|
333
|
+
async listDirectory(path: string) {
|
|
334
|
+
const res = await fetch(`/api/files?action=list&path=${encodeURIComponent(path)}`);
|
|
335
|
+
return res.json();
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
async getFolderTree(path = '/', depth = 3) {
|
|
339
|
+
const res = await fetch(`/api/files?action=tree&path=${encodeURIComponent(path)}&depth=${depth}`);
|
|
340
|
+
return res.json();
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
async createDirectory(path: string) {
|
|
344
|
+
const res = await fetch('/api/files', {
|
|
345
|
+
method: 'POST',
|
|
346
|
+
headers: { 'Content-Type': 'application/json' },
|
|
347
|
+
body: JSON.stringify({ action: 'createDirectory', path }),
|
|
348
|
+
});
|
|
349
|
+
return res.json();
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
async uploadFile(file: File, remotePath: string) {
|
|
353
|
+
const formData = new FormData();
|
|
354
|
+
formData.append('file', file);
|
|
355
|
+
formData.append('path', remotePath);
|
|
356
|
+
const res = await fetch('/api/files/upload', {
|
|
357
|
+
method: 'POST',
|
|
358
|
+
body: formData,
|
|
359
|
+
});
|
|
360
|
+
return res.json();
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
async downloadFile(path: string) {
|
|
364
|
+
const res = await fetch(`/api/files/download?path=${encodeURIComponent(path)}`);
|
|
365
|
+
if (!res.ok) {
|
|
366
|
+
const error = await res.json();
|
|
367
|
+
return { success: false, error: error.error };
|
|
368
|
+
}
|
|
369
|
+
const blob = await res.blob();
|
|
370
|
+
return { success: true, data: blob };
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
async deleteFile(path: string) {
|
|
374
|
+
const res = await fetch('/api/files', {
|
|
375
|
+
method: 'POST',
|
|
376
|
+
headers: { 'Content-Type': 'application/json' },
|
|
377
|
+
body: JSON.stringify({ action: 'deleteFile', path }),
|
|
378
|
+
});
|
|
379
|
+
return res.json();
|
|
380
|
+
},
|
|
381
|
+
|
|
382
|
+
async renameFile(path: string, newName: string) {
|
|
383
|
+
const res = await fetch('/api/files', {
|
|
384
|
+
method: 'POST',
|
|
385
|
+
headers: { 'Content-Type': 'application/json' },
|
|
386
|
+
body: JSON.stringify({ action: 'renameFile', path, newName }),
|
|
387
|
+
});
|
|
388
|
+
return res.json();
|
|
389
|
+
},
|
|
390
|
+
|
|
391
|
+
async renameFolder(path: string, newName: string) {
|
|
392
|
+
const res = await fetch('/api/files', {
|
|
393
|
+
method: 'POST',
|
|
394
|
+
headers: { 'Content-Type': 'application/json' },
|
|
395
|
+
body: JSON.stringify({ action: 'renameFolder', path, newName }),
|
|
396
|
+
});
|
|
397
|
+
return res.json();
|
|
398
|
+
},
|
|
399
|
+
|
|
400
|
+
async moveItem(sourcePath: string, destinationPath: string) {
|
|
401
|
+
const res = await fetch('/api/files', {
|
|
402
|
+
method: 'POST',
|
|
403
|
+
headers: { 'Content-Type': 'application/json' },
|
|
404
|
+
body: JSON.stringify({ action: 'moveItem', sourcePath, destinationPath }),
|
|
405
|
+
});
|
|
406
|
+
return res.json();
|
|
407
|
+
},
|
|
408
|
+
|
|
409
|
+
async removeDirectory(path: string, recursive = false) {
|
|
410
|
+
const res = await fetch('/api/files', {
|
|
411
|
+
method: 'POST',
|
|
412
|
+
headers: { 'Content-Type': 'application/json' },
|
|
413
|
+
body: JSON.stringify({ action: 'removeDirectory', path, recursive }),
|
|
414
|
+
});
|
|
415
|
+
return res.json();
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### 4.3 Create FileBrowser Page
|
|
422
|
+
|
|
423
|
+
- [ ] Create `app/files/page.tsx`:
|
|
424
|
+
```typescript
|
|
425
|
+
'use client';
|
|
426
|
+
|
|
427
|
+
import { FileBrowser } from 'hazo_files/ui';
|
|
428
|
+
import { createFileAPI } from '@/lib/file-api';
|
|
429
|
+
|
|
430
|
+
export default function FilesPage() {
|
|
431
|
+
const api = createFileAPI();
|
|
432
|
+
|
|
433
|
+
return (
|
|
434
|
+
<div className="container mx-auto p-4">
|
|
435
|
+
<h1 className="text-2xl font-bold mb-4">File Manager</h1>
|
|
436
|
+
<div className="h-screen">
|
|
437
|
+
<FileBrowser
|
|
438
|
+
api={api}
|
|
439
|
+
initialPath="/"
|
|
440
|
+
showPreview={true}
|
|
441
|
+
showTree={true}
|
|
442
|
+
viewMode="grid"
|
|
443
|
+
onError={(error) => console.error('File browser error:', error)}
|
|
444
|
+
onNavigate={(path) => console.log('Navigated to:', path)}
|
|
445
|
+
/>
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### 4.4 Test UI
|
|
453
|
+
|
|
454
|
+
- [ ] Navigate to http://localhost:3000/files
|
|
455
|
+
|
|
456
|
+
- [ ] Test UI operations:
|
|
457
|
+
- [ ] Navigate folders
|
|
458
|
+
- [ ] Create new folder
|
|
459
|
+
- [ ] Upload file
|
|
460
|
+
- [ ] Download file
|
|
461
|
+
- [ ] Rename file/folder
|
|
462
|
+
- [ ] Delete file/folder
|
|
463
|
+
- [ ] View file preview
|
|
464
|
+
|
|
465
|
+
**Checkpoint**: UI components are fully functional.
|
|
466
|
+
|
|
467
|
+
## Part 4.5: Naming Rule Configurator (Optional)
|
|
468
|
+
|
|
469
|
+
### 4.5.1 Create Naming Configuration Page
|
|
470
|
+
|
|
471
|
+
- [ ] Create `app/naming/page.tsx`:
|
|
472
|
+
```typescript
|
|
473
|
+
'use client';
|
|
474
|
+
|
|
475
|
+
import { useState } from 'react';
|
|
476
|
+
import { NamingRuleConfigurator } from 'hazo_files/ui';
|
|
477
|
+
import type { NamingVariable, NamingRuleSchema } from 'hazo_files/ui';
|
|
478
|
+
|
|
479
|
+
export default function NamingConfigPage() {
|
|
480
|
+
const [schema, setSchema] = useState<NamingRuleSchema | null>(null);
|
|
481
|
+
|
|
482
|
+
// Define user-specific variables
|
|
483
|
+
const userVariables: NamingVariable[] = [
|
|
484
|
+
{
|
|
485
|
+
variable_name: 'project_name',
|
|
486
|
+
description: 'Name of the project',
|
|
487
|
+
example_value: 'WebApp',
|
|
488
|
+
category: 'user',
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
variable_name: 'client_id',
|
|
492
|
+
description: 'Client identifier',
|
|
493
|
+
example_value: 'ACME',
|
|
494
|
+
category: 'user',
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
variable_name: 'document_type',
|
|
498
|
+
description: 'Type of document',
|
|
499
|
+
example_value: 'Proposal',
|
|
500
|
+
category: 'user',
|
|
501
|
+
},
|
|
502
|
+
];
|
|
503
|
+
|
|
504
|
+
const handleSchemaChange = (newSchema: NamingRuleSchema) => {
|
|
505
|
+
setSchema(newSchema);
|
|
506
|
+
console.log('Schema updated:', newSchema);
|
|
507
|
+
// Save to database or localStorage
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
const handleExport = (exportSchema: NamingRuleSchema) => {
|
|
511
|
+
const blob = new Blob([JSON.stringify(exportSchema, null, 2)], {
|
|
512
|
+
type: 'application/json',
|
|
513
|
+
});
|
|
514
|
+
const url = URL.createObjectURL(blob);
|
|
515
|
+
const a = document.createElement('a');
|
|
516
|
+
a.href = url;
|
|
517
|
+
a.download = 'naming-rule.json';
|
|
518
|
+
a.click();
|
|
519
|
+
URL.revokeObjectURL(url);
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
const handleImport = async () => {
|
|
523
|
+
const input = document.createElement('input');
|
|
524
|
+
input.type = 'file';
|
|
525
|
+
input.accept = '.json';
|
|
526
|
+
input.onchange = async (e: Event) => {
|
|
527
|
+
const file = (e.target as HTMLInputElement).files?.[0];
|
|
528
|
+
if (!file) return;
|
|
529
|
+
|
|
530
|
+
const text = await file.text();
|
|
531
|
+
const importedSchema = JSON.parse(text);
|
|
532
|
+
setSchema(importedSchema);
|
|
533
|
+
};
|
|
534
|
+
input.click();
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
return (
|
|
538
|
+
<div className="container mx-auto p-6">
|
|
539
|
+
<h1 className="text-3xl font-bold mb-6">Naming Rule Configurator</h1>
|
|
540
|
+
|
|
541
|
+
<div className="bg-white rounded-lg shadow-lg p-6">
|
|
542
|
+
<NamingRuleConfigurator
|
|
543
|
+
variables={userVariables}
|
|
544
|
+
initialSchema={schema || undefined}
|
|
545
|
+
onChange={handleSchemaChange}
|
|
546
|
+
onExport={handleExport}
|
|
547
|
+
sampleFileName="proposal.pdf"
|
|
548
|
+
/>
|
|
549
|
+
</div>
|
|
550
|
+
|
|
551
|
+
{schema && (
|
|
552
|
+
<div className="mt-6 bg-gray-50 rounded-lg p-4">
|
|
553
|
+
<h2 className="text-xl font-semibold mb-2">Current Schema</h2>
|
|
554
|
+
<pre className="bg-white p-4 rounded border overflow-auto">
|
|
555
|
+
{JSON.stringify(schema, null, 2)}
|
|
556
|
+
</pre>
|
|
557
|
+
</div>
|
|
558
|
+
)}
|
|
559
|
+
</div>
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### 4.5.2 Test Naming Rule Generation
|
|
565
|
+
|
|
566
|
+
- [ ] Create test utility `lib/test-naming.ts`:
|
|
567
|
+
```typescript
|
|
568
|
+
import {
|
|
569
|
+
hazo_files_generate_file_name,
|
|
570
|
+
hazo_files_generate_folder_name,
|
|
571
|
+
type NamingRuleSchema,
|
|
572
|
+
} from 'hazo_files';
|
|
573
|
+
|
|
574
|
+
export function testNamingRule(
|
|
575
|
+
schema: NamingRuleSchema,
|
|
576
|
+
variables: Record<string, string>
|
|
577
|
+
) {
|
|
578
|
+
// Test file name generation
|
|
579
|
+
const fileResult = hazo_files_generate_file_name(
|
|
580
|
+
schema,
|
|
581
|
+
variables,
|
|
582
|
+
'document.pdf',
|
|
583
|
+
{
|
|
584
|
+
counterValue: 1,
|
|
585
|
+
preserveExtension: true,
|
|
586
|
+
date: new Date(),
|
|
587
|
+
}
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
// Test folder name generation
|
|
591
|
+
const folderResult = hazo_files_generate_folder_name(schema, variables);
|
|
592
|
+
|
|
593
|
+
return {
|
|
594
|
+
file: fileResult,
|
|
595
|
+
folder: folderResult,
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### 4.5.3 Verify Naming Configuration
|
|
601
|
+
|
|
602
|
+
- [ ] Navigate to http://localhost:3000/naming
|
|
603
|
+
|
|
604
|
+
- [ ] Test configurator operations:
|
|
605
|
+
- [ ] Drag date variables to file pattern
|
|
606
|
+
- [ ] Drag user variables to patterns
|
|
607
|
+
- [ ] Add literal text separators (-, _, space)
|
|
608
|
+
- [ ] Reorder segments within pattern
|
|
609
|
+
- [ ] Remove segments
|
|
610
|
+
- [ ] Clear entire pattern
|
|
611
|
+
- [ ] Verify live preview updates
|
|
612
|
+
|
|
613
|
+
- [ ] Test undo/redo:
|
|
614
|
+
- [ ] Make several changes
|
|
615
|
+
- [ ] Press Ctrl+Z (Cmd+Z on Mac) to undo
|
|
616
|
+
- [ ] Press Ctrl+Y (Cmd+Y on Mac) to redo
|
|
617
|
+
- [ ] Verify pattern history works
|
|
618
|
+
|
|
619
|
+
- [ ] Test export/import:
|
|
620
|
+
- [ ] Build a pattern
|
|
621
|
+
- [ ] Export as JSON file
|
|
622
|
+
- [ ] Clear the pattern
|
|
623
|
+
- [ ] Import the JSON file
|
|
624
|
+
- [ ] Verify pattern is restored
|
|
625
|
+
|
|
626
|
+
**Checkpoint**: Naming rule configurator is working.
|
|
627
|
+
|
|
628
|
+
## Part 5: Google Drive Setup (Optional)
|
|
629
|
+
|
|
630
|
+
### 5.1 Google Cloud Console Setup
|
|
631
|
+
|
|
632
|
+
- [ ] Go to [Google Cloud Console](https://console.cloud.google.com)
|
|
633
|
+
|
|
634
|
+
- [ ] Create new project or select existing project
|
|
635
|
+
|
|
636
|
+
- [ ] Enable Google Drive API:
|
|
637
|
+
- [ ] Navigate to "APIs & Services" > "Library"
|
|
638
|
+
- [ ] Search for "Google Drive API"
|
|
639
|
+
- [ ] Click "Enable"
|
|
640
|
+
|
|
641
|
+
- [ ] Create OAuth 2.0 credentials:
|
|
642
|
+
- [ ] Navigate to "APIs & Services" > "Credentials"
|
|
643
|
+
- [ ] Click "Create Credentials" > "OAuth client ID"
|
|
644
|
+
- [ ] Choose "Web application"
|
|
645
|
+
- [ ] Set name: "hazo_files"
|
|
646
|
+
- [ ] Add authorized redirect URI: `http://localhost:3000/api/auth/google/callback`
|
|
647
|
+
- [ ] Click "Create"
|
|
648
|
+
- [ ] Copy Client ID and Client Secret
|
|
649
|
+
|
|
650
|
+
### 5.2 Environment Variables
|
|
651
|
+
|
|
652
|
+
- [ ] Create `.env.local` file:
|
|
653
|
+
```bash
|
|
654
|
+
GOOGLE_DRIVE_CLIENT_ID=your-client-id.apps.googleusercontent.com
|
|
655
|
+
GOOGLE_DRIVE_CLIENT_SECRET=GOCSPX-xxxxx
|
|
656
|
+
GOOGLE_DRIVE_REDIRECT_URI=http://localhost:3000/api/auth/google/callback
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
- [ ] Update `hazo_files_config.ini`:
|
|
660
|
+
```ini
|
|
661
|
+
[general]
|
|
662
|
+
provider = google_drive
|
|
663
|
+
|
|
664
|
+
[google_drive]
|
|
665
|
+
client_id =
|
|
666
|
+
client_secret =
|
|
667
|
+
redirect_uri = http://localhost:3000/api/auth/google/callback
|
|
668
|
+
refresh_token =
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
- [ ] Add `.env.local` to `.gitignore`:
|
|
672
|
+
```bash
|
|
673
|
+
echo ".env.local" >> .gitignore
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
### 5.3 Database Setup for Token Storage
|
|
677
|
+
|
|
678
|
+
#### PostgreSQL Setup
|
|
679
|
+
|
|
680
|
+
- [ ] Create database:
|
|
681
|
+
```sql
|
|
682
|
+
CREATE DATABASE hazo_files_db;
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
- [ ] Create user:
|
|
686
|
+
```sql
|
|
687
|
+
CREATE USER hazo_files_user WITH PASSWORD 'your_secure_password';
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
- [ ] Grant privileges:
|
|
691
|
+
```sql
|
|
692
|
+
GRANT ALL PRIVILEGES ON DATABASE hazo_files_db TO hazo_files_user;
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
- [ ] Connect to database:
|
|
696
|
+
```bash
|
|
697
|
+
psql -U hazo_files_user -d hazo_files_db
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
- [ ] Create tokens table:
|
|
701
|
+
```sql
|
|
702
|
+
CREATE TABLE google_drive_tokens (
|
|
703
|
+
id SERIAL PRIMARY KEY,
|
|
704
|
+
user_id VARCHAR(255) UNIQUE NOT NULL,
|
|
705
|
+
access_token TEXT NOT NULL,
|
|
706
|
+
refresh_token TEXT NOT NULL,
|
|
707
|
+
expiry_date BIGINT,
|
|
708
|
+
scope TEXT,
|
|
709
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
710
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
711
|
+
);
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
- [ ] Create index:
|
|
715
|
+
```sql
|
|
716
|
+
CREATE INDEX idx_tokens_user_id ON google_drive_tokens(user_id);
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
- [ ] Grant table privileges:
|
|
720
|
+
```sql
|
|
721
|
+
GRANT ALL PRIVILEGES ON TABLE google_drive_tokens TO hazo_files_user;
|
|
722
|
+
GRANT USAGE, SELECT ON SEQUENCE google_drive_tokens_id_seq TO hazo_files_user;
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
#### SQLite Setup (Alternative)
|
|
726
|
+
|
|
727
|
+
- [ ] Create database file:
|
|
728
|
+
```bash
|
|
729
|
+
touch hazo_files.db
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
- [ ] Create tokens table:
|
|
733
|
+
```bash
|
|
734
|
+
sqlite3 hazo_files.db << 'EOF'
|
|
735
|
+
CREATE TABLE google_drive_tokens (
|
|
736
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
737
|
+
user_id TEXT UNIQUE NOT NULL,
|
|
738
|
+
access_token TEXT NOT NULL,
|
|
739
|
+
refresh_token TEXT NOT NULL,
|
|
740
|
+
expiry_date INTEGER,
|
|
741
|
+
scope TEXT,
|
|
742
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
743
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
744
|
+
);
|
|
745
|
+
|
|
746
|
+
CREATE INDEX idx_tokens_user_id ON google_drive_tokens(user_id);
|
|
747
|
+
EOF
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### 5.4 Implement OAuth Flow
|
|
751
|
+
|
|
752
|
+
- [ ] Create `app/api/auth/google/route.ts`:
|
|
753
|
+
```typescript
|
|
754
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
755
|
+
import { createFileManager, GoogleDriveModule } from 'hazo_files';
|
|
756
|
+
|
|
757
|
+
export async function GET(request: NextRequest) {
|
|
758
|
+
const fm = createFileManager({
|
|
759
|
+
config: {
|
|
760
|
+
provider: 'google_drive',
|
|
761
|
+
google_drive: {
|
|
762
|
+
clientId: process.env.GOOGLE_DRIVE_CLIENT_ID!,
|
|
763
|
+
clientSecret: process.env.GOOGLE_DRIVE_CLIENT_SECRET!,
|
|
764
|
+
redirectUri: process.env.GOOGLE_DRIVE_REDIRECT_URI!,
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
await fm.initialize();
|
|
770
|
+
const module = fm.getModule() as GoogleDriveModule;
|
|
771
|
+
const authUrl = module.getAuth().getAuthUrl();
|
|
772
|
+
|
|
773
|
+
return NextResponse.redirect(authUrl);
|
|
774
|
+
}
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
- [ ] Create `app/api/auth/google/callback/route.ts`:
|
|
778
|
+
```typescript
|
|
779
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
780
|
+
import { createFileManager, GoogleDriveModule } from 'hazo_files';
|
|
781
|
+
|
|
782
|
+
export async function GET(request: NextRequest) {
|
|
783
|
+
const { searchParams } = new URL(request.url);
|
|
784
|
+
const code = searchParams.get('code');
|
|
785
|
+
|
|
786
|
+
if (!code) {
|
|
787
|
+
return NextResponse.json({ error: 'No code provided' }, { status: 400 });
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const fm = createFileManager({
|
|
791
|
+
config: {
|
|
792
|
+
provider: 'google_drive',
|
|
793
|
+
google_drive: {
|
|
794
|
+
clientId: process.env.GOOGLE_DRIVE_CLIENT_ID!,
|
|
795
|
+
clientSecret: process.env.GOOGLE_DRIVE_CLIENT_SECRET!,
|
|
796
|
+
redirectUri: process.env.GOOGLE_DRIVE_REDIRECT_URI!,
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
await fm.initialize();
|
|
802
|
+
const module = fm.getModule() as GoogleDriveModule;
|
|
803
|
+
|
|
804
|
+
// Exchange code for tokens
|
|
805
|
+
const tokens = await module.getAuth().exchangeCodeForTokens(code);
|
|
806
|
+
|
|
807
|
+
// Store tokens in database (implement this)
|
|
808
|
+
// await storeTokens(userId, tokens);
|
|
809
|
+
|
|
810
|
+
return NextResponse.redirect('/files?connected=true');
|
|
811
|
+
}
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
### 5.5 Test Google Drive Integration
|
|
815
|
+
|
|
816
|
+
- [ ] Navigate to http://localhost:3000/api/auth/google
|
|
817
|
+
|
|
818
|
+
- [ ] Grant permissions when prompted
|
|
819
|
+
|
|
820
|
+
- [ ] Verify redirect to `/files?connected=true`
|
|
821
|
+
|
|
822
|
+
- [ ] Test Google Drive operations:
|
|
823
|
+
- [ ] List files
|
|
824
|
+
- [ ] Create folder
|
|
825
|
+
- [ ] Upload file
|
|
826
|
+
- [ ] Download file
|
|
827
|
+
|
|
828
|
+
**Checkpoint**: Google Drive integration is complete.
|
|
829
|
+
|
|
830
|
+
## Part 6: Production Deployment
|
|
831
|
+
|
|
832
|
+
### 6.1 Environment Configuration
|
|
833
|
+
|
|
834
|
+
- [ ] Set production environment variables on hosting platform
|
|
835
|
+
|
|
836
|
+
- [ ] Update redirect URI for production:
|
|
837
|
+
```
|
|
838
|
+
https://yourdomain.com/api/auth/google/callback
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
- [ ] Add production redirect URI to Google Cloud Console
|
|
842
|
+
|
|
843
|
+
### 6.2 Security Checklist
|
|
844
|
+
|
|
845
|
+
- [ ] Environment variables are not committed to git
|
|
846
|
+
- [ ] Database credentials are secure
|
|
847
|
+
- [ ] API routes have authentication/authorization
|
|
848
|
+
- [ ] File size limits are configured
|
|
849
|
+
- [ ] Extension whitelist is configured
|
|
850
|
+
- [ ] CORS is properly configured
|
|
851
|
+
- [ ] Rate limiting is implemented
|
|
852
|
+
|
|
853
|
+
### 6.3 Performance Optimization
|
|
854
|
+
|
|
855
|
+
- [ ] Enable caching for file listings
|
|
856
|
+
- [ ] Implement pagination for large directories
|
|
857
|
+
- [ ] Configure CDN for static file downloads (if applicable)
|
|
858
|
+
- [ ] Set up monitoring and logging
|
|
859
|
+
|
|
860
|
+
### 6.4 Backup Strategy
|
|
861
|
+
|
|
862
|
+
- [ ] Configure automated backups for local storage
|
|
863
|
+
- [ ] Set up database backups for tokens
|
|
864
|
+
- [ ] Test backup restoration process
|
|
865
|
+
|
|
866
|
+
**Checkpoint**: Application is production-ready.
|
|
867
|
+
|
|
868
|
+
## Verification
|
|
869
|
+
|
|
870
|
+
Run through this final checklist to ensure everything is working:
|
|
871
|
+
|
|
872
|
+
- [ ] FileManager initializes without errors
|
|
873
|
+
- [ ] Can create directories
|
|
874
|
+
- [ ] Can upload files
|
|
875
|
+
- [ ] Can download files
|
|
876
|
+
- [ ] Can delete files
|
|
877
|
+
- [ ] Can rename files/folders
|
|
878
|
+
- [ ] Can move files
|
|
879
|
+
- [ ] Can list directories
|
|
880
|
+
- [ ] UI displays file browser (if using UI components)
|
|
881
|
+
- [ ] Google Drive authentication works (if using Google Drive)
|
|
882
|
+
- [ ] Tokens are persisted (if using Google Drive)
|
|
883
|
+
- [ ] All API endpoints return correct responses
|
|
884
|
+
- [ ] Error handling works correctly
|
|
885
|
+
- [ ] File size limits are enforced
|
|
886
|
+
- [ ] Extension filtering works
|
|
887
|
+
|
|
888
|
+
## Troubleshooting
|
|
889
|
+
|
|
890
|
+
### Common Issues
|
|
891
|
+
|
|
892
|
+
**Issue**: FileManager fails to initialize
|
|
893
|
+
|
|
894
|
+
- Check configuration file syntax
|
|
895
|
+
- Verify environment variables are set
|
|
896
|
+
- Check file permissions on storage directory
|
|
897
|
+
|
|
898
|
+
**Issue**: Upload fails with "Extension not allowed"
|
|
899
|
+
|
|
900
|
+
- Check `allowed_extensions` in config
|
|
901
|
+
- Ensure file extension matches allowed list
|
|
902
|
+
- Leave `allowed_extensions` empty to allow all types
|
|
903
|
+
|
|
904
|
+
**Issue**: Google Drive authentication fails
|
|
905
|
+
|
|
906
|
+
- Verify OAuth credentials are correct
|
|
907
|
+
- Check redirect URI matches exactly
|
|
908
|
+
- Ensure Google Drive API is enabled
|
|
909
|
+
|
|
910
|
+
**Issue**: "ENOENT" error when accessing files
|
|
911
|
+
|
|
912
|
+
- Verify `base_path` directory exists
|
|
913
|
+
- Check file permissions
|
|
914
|
+
- Ensure path starts with `/`
|
|
915
|
+
|
|
916
|
+
**Issue**: FileBrowser UI not rendering
|
|
917
|
+
|
|
918
|
+
- Check React version (must be 18+)
|
|
919
|
+
- Verify hazo_files/ui is imported correctly
|
|
920
|
+
- Check browser console for errors
|
|
921
|
+
|
|
922
|
+
## Next Steps
|
|
923
|
+
|
|
924
|
+
- [ ] Review [README.md](README.md) for advanced usage examples
|
|
925
|
+
- [ ] Read [TECHDOC.md](TECHDOC.md) for technical details
|
|
926
|
+
- [ ] Check [docs/ADDING_MODULES.md](docs/ADDING_MODULES.md) to create custom storage modules
|
|
927
|
+
- [ ] Explore test-app for complete implementation example
|
|
928
|
+
|
|
929
|
+
---
|
|
930
|
+
|
|
931
|
+
Congratulations! You have successfully set up hazo_files. For support, visit the [GitHub repository](https://github.com/pub12/hazo_files).
|