React SPA + StorageVault (S3 staatiline majutus)

    Selles juhendis ehitame React SPA rakenduse, mis kasutab Pilvio StorageVault'i (S3) staatiliste failide majutamiseks ja failide üleslaadimiseks.

    ⏳ CDN tulemas: Pilvio CDN on arenduses. Praegu saab staatilisi faile serveerida otse StorageVault'i S3 kaudu. CDN-i saabudes saad hõlpsalt lisada vahemälu ja geograafilise levitamise.

    Mida ehitame

    • React SPA, mis suhtleb Pilvio backendiga
    • Failide üleslaadimine otse StorageVault'i (eelsigneeritud URL-id)
    • Staatiline deploy S3 bucketisse

    Eeldused


    1. samm: React projekti loomine

    npm create vite@latest pilvio-react-app -- --template react
    cd pilvio-react-app
    npm install
    npm install axios
    

    2. samm: Pilvio API kliendi loomine

    Loo fail src/lib/pilvio-client.js:

    import axios from 'axios';
    
    // Backend API URL (mitte otse Pilvio API, vaid sinu backend)
    const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000';
    
    const api = axios.create({
      baseURL: API_BASE,
      headers: {
        'Content-Type': 'application/json',
      },
    });
    
    // Failide üleslaadimine backend proxy kaudu
    export async function uploadFile(file) {
      const formData = new FormData();
      formData.append('file', file);
    
      const response = await api.post('/api/v1/files/upload', formData, {
        headers: { 'Content-Type': 'multipart/form-data' },
      });
      return response.data;
    }
    
    // Failide loetelu
    export async function listFiles(prefix = 'uploads/') {
      const response = await api.get('/api/v1/files', {
        params: { prefix },
      });
      return response.data;
    }
    
    // Allalaadimis-URL hankimine (eelsigneeritud S3 URL)
    export async function getDownloadUrl(fileKey) {
      const response = await api.get(`/api/v1/files/download-url/${fileKey}`);
      return response.data;
    }
    
    // Faili kustutamine
    export async function deleteFile(fileKey) {
      const response = await api.delete(`/api/v1/files/${fileKey}`);
      return response.data;
    }
    
    export default api;
    

    Turvalisus: Ära pane kunagi Pilvio API tokenit ega S3 võtmeid React rakendusse! Kõik Pilvio API päringud peavad käima läbi sinu backend serveri.

    3. samm: Failihalduse komponent

    Loo fail src/components/FileManager.jsx:

    import { useState, useEffect } from 'react';
    import { uploadFile, listFiles, getDownloadUrl, deleteFile } from '../lib/pilvio-client';
    
    export default function FileManager() {
      const [files, setFiles] = useState([]);
      const [uploading, setUploading] = useState(false);
      const [error, setError] = useState(null);
    
      useEffect(() => {
        loadFiles();
      }, []);
    
      async function loadFiles() {
        try {
          const data = await listFiles();
          setFiles(data.files || []);
        } catch (err) {
          setError('Failide laadimine ebaõnnestus');
        }
      }
    
      async function handleUpload(event) {
        const file = event.target.files[0];
        if (!file) return;
    
        setUploading(true);
        setError(null);
    
        try {
          await uploadFile(file);
          await loadFiles();
        } catch (err) {
          setError('Üleslaadimine ebaõnnestus');
        } finally {
          setUploading(false);
        }
      }
    
      async function handleDownload(fileKey) {
        try {
          const data = await getDownloadUrl(fileKey);
          window.open(data.url, '_blank');
        } catch (err) {
          setError('Allalaadimine ebaõnnestus');
        }
      }
    
      async function handleDelete(fileKey) {
        if (!confirm('Kas oled kindel?')) return;
        try {
          await deleteFile(fileKey);
          await loadFiles();
        } catch (err) {
          setError('Kustutamine ebaõnnestus');
        }
      }
    
      function formatSize(bytes) {
        if (bytes < 1024) return `${bytes} B`;
        if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
        return `${(bytes / 1048576).toFixed(1)} MB`;
      }
    
      return (
        <div style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>
          <h1>StorageVault failihaldus</h1>
    
          {error && (
            <div style={{ color: 'red', padding: '10px', marginBottom: '10px' }}>
              {error}
            </div>
          )}
    
          <div style={{ marginBottom: '20px' }}>
            <input type="file" onChange={handleUpload} disabled={uploading} />
            {uploading && <span> Laadin üles...</span>}
          </div>
    
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead>
              <tr>
                <th style={{ textAlign: 'left', padding: '8px' }}>Fail</th>
                <th style={{ textAlign: 'right', padding: '8px' }}>Suurus</th>
                <th style={{ textAlign: 'right', padding: '8px' }}>Tegevused</th>
              </tr>
            </thead>
            <tbody>
              {files.map((file) => (
                <tr key={file.key} style={{ borderTop: '1px solid #eee' }}>
                  <td style={{ padding: '8px' }}>{file.key.split('/').pop()}</td>
                  <td style={{ textAlign: 'right', padding: '8px' }}>{formatSize(file.size)}</td>
                  <td style={{ textAlign: 'right', padding: '8px' }}>
                    <button onClick={() => handleDownload(file.key)}>Lae alla</button>{' '}
                    <button onClick={() => handleDelete(file.key)}>Kustuta</button>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
    
          {files.length === 0 && <p>Faile ei ole. Laadi midagi üles!</p>}
        </div>
      );
    }
    

    4. samm: Keskkonnamuutujad

    Loo fail .env:

    VITE_API_URL=https://sinu-api-domeen.ee
    

    5. samm: Staatiline deploy StorageVault'i (S3)

    Ehita React rakendus ja laadi üles S3 bucketisse:

    # Ehita
    npm run build
    
    # Laadi dist/ sisu S3-sse (AWS CLI Pilvio endpointiga)
    aws s3 sync dist/ s3://minu-react-app/ \
      --endpoint-url https://s3.pilvio.com:8080 \
      --delete
    
    # Sea index.html Content-Type
    aws s3 cp s3://minu-react-app/index.html s3://minu-react-app/index.html \
      --endpoint-url https://s3.pilvio.com:8080 \
      --content-type "text/html" \
      --metadata-directive REPLACE
    

    Alternatiiv: Deploy skript

    Loo fail deploy.sh:

    #!/bin/bash
    set -e
    
    echo "Ehitan React rakendust..."
    npm run build
    
    echo "Laadin üles StorageVault'i..."
    aws s3 sync dist/ s3://minu-react-app/ \
      --endpoint-url https://s3.pilvio.com:8080 \
      --delete \
      --cache-control "public, max-age=31536000" \
      --exclude "index.html"
    
    # index.html ilma vahemäluta (et uus versioon oleks kohe näha)
    aws s3 cp dist/index.html s3://minu-react-app/index.html \
      --endpoint-url https://s3.pilvio.com:8080 \
      --content-type "text/html" \
      --cache-control "no-cache"
    
    echo "Deploy valmis!"
    
    chmod +x deploy.sh
    ./deploy.sh
    

    Alternatiiv: SPA majutamine Pilvio VM + Nginx

    Kui S3 staatilise majutuse seadistamine on keeruline, saab SPA-d serveerida ka Nginx kaudu VM-il:

    # Kopeeri ehitatud failid serverisse
    scp -r dist/* deploy@SINU_FLOATING_IP:/var/www/react-app/
    
    # Nginx seadistus SPA jaoks
    sudo tee /etc/nginx/sites-available/react-app <<'EOF'
    server {
        listen 80;
        server_name sinu-domeen.ee;
        root /var/www/react-app;
        index index.html;
    
        location / {
            try_files $uri $uri/ /index.html;
        }
    
        location /assets/ {
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
    }
    EOF
    

    ⏳ CDN (tulemas): Pilvio CDN võimaldab tulevikus lisada S3 bucketile automaatse vahemälu ja edge-serveerimise. Praegune S3-põhine lahendus töötab hästi ja CDN-i lisamine nõuab ainult URL-i muutmist.

    Järgmised sammud: Ühenda backend API-ga (Node.js või FastAPI) ja lisa andmebaas.