Skip to content

Commit c184dda

Browse files
committed
iproved intro
1 parent e53c980 commit c184dda

2 files changed

Lines changed: 225 additions & 29 deletions

File tree

docs/website/css/intro.css

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,11 +557,104 @@ footer {
557557
text-decoration: underline;
558558
}
559559

560+
/* Section Subtitles */
561+
.section-subtitle {
562+
color: var(--text-heading);
563+
font-size: 1.2rem;
564+
margin: 32px 0 20px;
565+
padding-bottom: 8px;
566+
border-bottom: 1px solid var(--border-color);
567+
}
568+
569+
/* Setup Row */
570+
.setup-row {
571+
grid-template-columns: repeat(3, 1fr);
572+
}
573+
574+
/* Quickstart Code Section */
575+
.quickstart-code {
576+
background: var(--bg-code);
577+
border: 1px solid var(--border-color);
578+
border-radius: 12px;
579+
overflow: hidden;
580+
margin-top: 16px;
581+
}
582+
583+
.code-header {
584+
display: flex;
585+
justify-content: space-between;
586+
align-items: center;
587+
padding: 16px 24px;
588+
background: var(--bg-sidebar);
589+
border-bottom: 1px solid var(--border-color);
590+
}
591+
592+
.code-title {
593+
color: var(--text-heading);
594+
font-weight: 600;
595+
font-size: 1rem;
596+
}
597+
598+
.code-lang {
599+
color: var(--accent-blue);
600+
font-size: 0.85rem;
601+
background: rgba(88, 166, 255, 0.1);
602+
padding: 4px 12px;
603+
border-radius: 4px;
604+
}
605+
606+
.quickstart-code .code-block.large {
607+
border: none;
608+
border-radius: 0;
609+
margin: 0;
610+
padding: 24px;
611+
max-height: 500px;
612+
overflow-y: auto;
613+
}
614+
615+
.quickstart-code .code-block.large pre {
616+
margin: 0;
617+
font-size: 0.8rem;
618+
line-height: 1.6;
619+
}
620+
621+
.code-comment {
622+
color: #6a9955;
623+
font-style: italic;
624+
}
625+
626+
.code-output {
627+
background: #1a1f2e;
628+
border-top: 1px solid var(--border-color);
629+
padding: 20px 24px;
630+
}
631+
632+
.output-header {
633+
color: var(--accent-green);
634+
font-size: 0.85rem;
635+
font-weight: 600;
636+
margin-bottom: 12px;
637+
text-transform: uppercase;
638+
letter-spacing: 0.5px;
639+
}
640+
641+
.code-output pre {
642+
margin: 0;
643+
font-family: 'Fira Code', monospace;
644+
font-size: 0.8rem;
645+
color: var(--text-primary);
646+
line-height: 1.5;
647+
}
648+
560649
/* Responsive */
561650
@media (max-width: 768px) {
562651
.hero {
563652
padding: 40px 24px;
564653
}
654+
655+
.setup-row {
656+
grid-template-columns: 1fr;
657+
}
565658

566659
.hero h1 {
567660
font-size: 2rem;

docs/website/intro.html

Lines changed: 132 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -416,62 +416,165 @@ <h3>📦 Content-Defined Chunking</h3>
416416
<!-- Getting Started Section -->
417417
<section id="getting-started" class="intro-section">
418418
<h2>Getting Started</h2>
419-
<div class="getting-started-grid">
419+
420+
<!-- Row 1: Setup -->
421+
<h3 class="section-subtitle">1. Setup Your Environment</h3>
422+
<div class="getting-started-grid setup-row">
420423
<div class="gs-card">
421424
<h3>🐳 Docker Compose</h3>
422-
<p>Quickest way to run a local Fula gateway:</p>
425+
<p>Quickest way to run locally:</p>
423426
<div class="code-block">
424427
<pre>git clone https://github.com/functionland/fula-api
425428
cd fula-api
426-
docker-compose up -d
427-
428-
# Gateway available at http://localhost:9000</pre>
429+
docker-compose up -d</pre>
429430
</div>
430431
</div>
431432
<div class="gs-card">
432433
<h3>🔧 From Source</h3>
433434
<p>Build and run with Cargo:</p>
434435
<div class="code-block">
435-
<pre>git clone https://github.com/functionland/fula-api
436-
cd fula-api
437-
cargo build --release
436+
<pre>cargo build --release
438437
./target/release/fula-cli serve</pre>
439438
</div>
440439
</div>
441440
<div class="gs-card">
442441
<h3>⚙️ Configuration</h3>
443-
<p>Set environment variables:</p>
442+
<p>Environment variables:</p>
444443
<div class="code-block">
445-
<pre># Gateway
446-
FULA_HOST=0.0.0.0
444+
<pre>FULA_HOST=0.0.0.0
447445
FULA_PORT=9000
448-
449-
# IPFS
450446
IPFS_API_URL=http://localhost:5001
451-
CLUSTER_API_URL=http://localhost:9094
452-
453-
# Auth
454447
JWT_SECRET=your-secret-key</pre>
455448
</div>
456449
</div>
457-
<div class="gs-card">
458-
<h3>🧪 Test with AWS CLI</h3>
459-
<p>Verify your setup:</p>
460-
<div class="code-block">
461-
<pre># Configure endpoint
462-
alias fula='aws --endpoint-url http://localhost:9000'
450+
</div>
463451

464-
# Create bucket
465-
fula s3 mb s3://test-bucket
452+
<!-- Row 2: Quick Start Code Example -->
453+
<h3 class="section-subtitle">2. Upload & Manage Photos (JavaScript)</h3>
454+
<div class="quickstart-code">
455+
<div class="code-header">
456+
<span class="code-title">📸 Complete Example: Encrypt, Upload, and List Photos</span>
457+
<span class="code-lang">JavaScript / TypeScript</span>
458+
</div>
459+
<div class="code-block large">
460+
<pre><span class="code-comment">// 1. Install dependencies</span>
461+
<span class="code-comment">// npm install @aws-sdk/client-s3 crypto-js</span>
466462

467-
# Upload file
468-
fula s3 cp ./file.txt s3://test-bucket/
463+
import { S3Client, PutObjectCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
469464

470-
# List files
471-
fula s3 ls s3://test-bucket/</pre>
472-
</div>
465+
<span class="code-comment">// 2. Configure the Fula client</span>
466+
const fula = new S3Client({
467+
endpoint: 'http://localhost:9000',
468+
region: 'us-east-1',
469+
credentials: {
470+
accessKeyId: 'YOUR_ACCESS_KEY',
471+
secretAccessKey: 'YOUR_SECRET_KEY',
472+
},
473+
forcePathStyle: true,
474+
});
475+
476+
const BUCKET = 'my-photos';
477+
478+
<span class="code-comment">// 3. Helper: Encrypt data before upload (client-side encryption)</span>
479+
async function encryptData(data: ArrayBuffer, password: string): Promise&lt;ArrayBuffer&gt; {
480+
const encoder = new TextEncoder();
481+
const keyMaterial = await crypto.subtle.importKey(
482+
'raw', encoder.encode(password), 'PBKDF2', false, ['deriveKey']
483+
);
484+
const key = await crypto.subtle.deriveKey(
485+
{ name: 'PBKDF2', salt: encoder.encode('fula-salt'), iterations: 100000, hash: 'SHA-256' },
486+
keyMaterial, { name: 'AES-GCM', length: 256 }, false, ['encrypt']
487+
);
488+
const iv = crypto.getRandomValues(new Uint8Array(12));
489+
const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, data);
490+
<span class="code-comment">// Prepend IV to encrypted data</span>
491+
const result = new Uint8Array(iv.length + encrypted.byteLength);
492+
result.set(iv);
493+
result.set(new Uint8Array(encrypted), iv.length);
494+
return result.buffer;
495+
}
496+
497+
<span class="code-comment">// 4. Upload an encrypted photo</span>
498+
async function uploadPhoto(filename: string, photoData: ArrayBuffer, password: string) {
499+
console.log(`📤 Encrypting and uploading: ${filename}`);
500+
501+
<span class="code-comment">// Encrypt locally BEFORE sending to gateway</span>
502+
const encryptedData = await encryptData(photoData, password);
503+
504+
await fula.send(new PutObjectCommand({
505+
Bucket: BUCKET,
506+
Key: `photos/${filename}`,
507+
Body: new Uint8Array(encryptedData),
508+
ContentType: 'application/octet-stream', <span class="code-comment">// Encrypted = opaque bytes</span>
509+
Metadata: {
510+
'x-amz-meta-encrypted': 'true',
511+
'x-amz-meta-original-type': 'image/jpeg',
512+
},
513+
}));
514+
515+
console.log(`✅ Uploaded: photos/${filename}`);
516+
}
517+
518+
<span class="code-comment">// 5. List all photos in the folder</span>
519+
async function listPhotos() {
520+
console.log('📂 Listing photos folder...');
521+
522+
const response = await fula.send(new ListObjectsV2Command({
523+
Bucket: BUCKET,
524+
Prefix: 'photos/',
525+
}));
526+
527+
const files = response.Contents || [];
528+
console.log(`Found ${files.length} photo(s):`);
529+
files.forEach(file =&gt; {
530+
console.log(` 📷 ${file.Key} (${file.Size} bytes)`);
531+
});
532+
533+
return files;
534+
}
535+
536+
<span class="code-comment">// 6. Run the complete workflow</span>
537+
async function main() {
538+
const password = 'my-secret-encryption-key';
539+
540+
<span class="code-comment">// Simulate photo data (in real app, read from file/camera)</span>
541+
const photo1 = new TextEncoder().encode('fake-jpeg-data-for-sunset.jpg');
542+
const photo2 = new TextEncoder().encode('fake-jpeg-data-for-beach.jpg');
543+
544+
<span class="code-comment">// Upload first photo (encrypted)</span>
545+
await uploadPhoto('sunset.jpg', photo1.buffer, password);
546+
547+
<span class="code-comment">// List folder - should show 1 photo</span>
548+
await listPhotos();
549+
<span class="code-comment">// Output: Found 1 photo(s): 📷 photos/sunset.jpg</span>
550+
551+
<span class="code-comment">// Upload second photo (encrypted)</span>
552+
await uploadPhoto('beach.jpg', photo2.buffer, password);
553+
554+
<span class="code-comment">// List folder again - should show 2 photos</span>
555+
await listPhotos();
556+
<span class="code-comment">// Output: Found 2 photo(s): 📷 photos/sunset.jpg, 📷 photos/beach.jpg</span>
557+
}
558+
559+
main().catch(console.error);</pre>
560+
</div>
561+
<div class="code-output">
562+
<div class="output-header">Expected Output</div>
563+
<pre>📤 Encrypting and uploading: sunset.jpg
564+
✅ Uploaded: photos/sunset.jpg
565+
📂 Listing photos folder...
566+
Found 1 photo(s):
567+
📷 photos/sunset.jpg (156 bytes)
568+
569+
📤 Encrypting and uploading: beach.jpg
570+
✅ Uploaded: photos/beach.jpg
571+
📂 Listing photos folder...
572+
Found 2 photo(s):
573+
📷 photos/sunset.jpg (156 bytes)
574+
📷 photos/beach.jpg (152 bytes)</pre>
473575
</div>
474576
</div>
577+
475578
<div class="next-steps">
476579
<h3>Next Steps</h3>
477580
<div class="next-links">

0 commit comments

Comments
 (0)