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.
@@ -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).