# 📁 Syamsu Files (SFiles)

**Penyimpanan file pribadi berbasis web** dengan tampilan pastel scrapbook yang modern dan nyaman. Menggunakan **Cloudflare R2** sebagai storage backend, dirancang untuk deployment mudah di **Apache Shared Hosting**.

![PHP](https://img.shields.io/badge/PHP-8.0+-8892BF?style=flat-square&logo=php&logoColor=white)
![React](https://img.shields.io/badge/React-18-61DAFB?style=flat-square&logo=react&logoColor=black)
![Tailwind](https://img.shields.io/badge/Tailwind_CSS-4-06B6D4?style=flat-square&logo=tailwindcss&logoColor=white)
![SQLite](https://img.shields.io/badge/SQLite-3-003B57?style=flat-square&logo=sqlite&logoColor=white)
![Cloudflare R2](https://img.shields.io/badge/Cloudflare-R2-F38020?style=flat-square&logo=cloudflare&logoColor=white)

---

## ✨ Fitur

| Fitur | Deskripsi |
|-------|-----------|
| 🔐 **Autentikasi** | Login single-user dengan password bcrypt hash, CSRF protection, rate limiting |
| 📂 **Folder** | Buat, rename, hapus, pindahkan folder — nested/bersarang |
| 📄 **Upload File** | Drag & drop + progress bar, upload langsung ke R2 via presigned URL |
| 📥 **Download** | Download file via presigned URL (backend tidak jadi bottleneck) |
| ✏️ **Rename** | Ganti nama tampilan file/folder tanpa memindahkan object di R2 |
| 🔗 **Berbagi Publik** | Buat tautan publik untuk file atau folder — siapa pun bisa mengunduh |
| 👁️ **Preview** | Preview gambar (JPG/PNG/WebP/GIF) dan PDF langsung di browser |
| 🗑️ **Tempat Sampah** | Soft delete — file/folder masuk trash dulu, bisa dipulihkan |
| 📦 **Pindahkan** | Pindahkan file/folder ke folder lain |
| 📊 **Storage Usage** | Informasi penggunaan storage + quota dengan progress bar |
| 🔒 **Ganti Password** | Ubah password akun dengan meter kekuatan password |
| 🌙 **Dark Mode** | Toggle tema Pastel Paper (terang) / Midnight (gelap) |
| 📱 **Responsive** | Mobile-first, nyaman di semua ukuran layar |
| 🖼️ **OpenGraph** | Dynamic OG image untuk link preview saat dibagikan ke sosial media |

---

## 📸 Screenshot

### File Manager
```
┌─────────────────────────────────────┐
│ 📂 File Manager          ● ● ●     │
├─────────────────────────────────────┤
│ [⬆ Unggah] [📁 Folder Baru] [🔄]  │
├─────────────────────────────────────┤
│ 📁 Documents              DIR  —   │
│ 📁 Photos                 DIR  —   │
│ 🖼️ foto.jpg               IMG 2MB  │
│ 📕 laporan.pdf             PDF 5MB  │
│ 🎬 video.mp4               VID 50MB │
├─────────────────────────────────────┤
│ 2 folder, 3 file    120MB / 1GB    │
└─────────────────────────────────────┘
│📁 Mulai│ File Saya │  🌙 👤 admin  │
└─────────────────────────────────────┘
```

---

## 🏗️ Arsitektur

```
Browser (React 18)
    │
    ├── Upload ──→ Presigned PUT URL ──→ Cloudflare R2
    ├── Download ──→ Presigned GET URL ──→ Cloudflare R2
    │
    └── API calls ──→ PHP Backend (MVC)
                          │
                          ├── SQLite (metadata, users)
                          └── R2 Signed Requests (HEAD, DELETE)
```

### Strategi Upload (Direct-to-R2)
1. Frontend minta presigned PUT URL ke backend (`POST /api/files/init-upload`)
2. Backend validasi (ekstensi, mime, ukuran, quota) → generate presigned URL
3. Frontend upload file langsung ke R2 menggunakan presigned URL + progress bar
4. Setelah selesai, frontend konfirmasi ke backend (`POST /api/files/complete-upload`)
5. Backend HEAD ke R2 untuk validasi → simpan metadata ke SQLite

---

## 📁 Struktur Folder

```
sfiles/
├── public/                    # Document root (arahkan domain ke sini)
│   ├── .htaccess              # Apache rewrite rules
│   ├── index.php              # Front controller
│   ├── favicon.svg            # Favicon
│   ├── og-image.php           # Dynamic OpenGraph image generator
│   └── assets/                # Build output (app.js, app.css)
│
├── app/
│   ├── Core/
│   │   ├── Router.php         # Simple pattern-based router
│   │   ├── DB.php             # SQLite PDO + auto-migrate + seed
│   │   ├── Auth.php           # Session authentication + rate limit
│   │   ├── Csrf.php           # CSRF token management
│   │   └── R2.php             # AWS Signature V4 for Cloudflare R2
│   │
│   ├── Controllers/
│   │   ├── AuthController.php     # Login, logout, CSRF, password
│   │   ├── FilesController.php    # Files & folders CRUD + share + trash
│   │   ├── PublicController.php   # Public share pages
│   │   └── AppController.php     # SPA shell
│   │
│   ├── Models/
│   │   ├── UserModel.php      # User queries
│   │   ├── FileModel.php      # File queries
│   │   └── FolderModel.php    # Folder queries
│   │
│   └── Views/
│       ├── public_share.php   # Public download page
│       └── public_error.php   # Error page (404, expired link)
│
├── config/
│   └── config.php             # Konfigurasi utama
│
├── data/                      # Auto-created, berisi SQLite database
│   └── sfiles.sqlite          # (auto-generated)
│
├── frontend/                  # React source (untuk development)
│   ├── package.json
│   ├── vite.config.js
│   ├── index.html
│   └── src/
│       ├── main.jsx
│       ├── styles.css         # Design system + Tailwind
│       ├── App.jsx            # App shell + taskbar
│       ├── api.js             # API client (axios)
│       ├── lib/
│       │   └── cn.js          # clsx + tailwind-merge utility
│       └── pages/
│           ├── Login.jsx
│           ├── Dashboard.jsx  # File manager + trash
│           └── Settings.jsx
│
└── README.md
```

---

## 🚀 Instalasi & Deployment

### Persyaratan
- **PHP 8.0+** dengan ekstensi: `pdo_sqlite`, `curl`, `mbstring`
- **Apache** dengan `mod_rewrite` aktif
- **Node.js 18+** (hanya untuk build frontend, tidak dibutuhkan di server)
- **Cloudflare R2** bucket + API credentials

### Langkah 1: Clone / Download

```bash
git clone https://github.com/username/sfiles.git
cd sfiles
```

### Langkah 2: Konfigurasi

Salin dan edit file konfigurasi:

```bash
cp config/config.php config/config.php.bak
nano config/config.php
```

Isi konfigurasi yang **wajib diubah**:

```php
return [
    // URL aplikasi (tanpa trailing slash)
    'APP_URL' => 'https://yourdomain.com',

    // Kredensial login awal (hanya untuk seed pertama kali)
    // Password akan di-hash otomatis dengan bcrypt
    'DEFAULT_USERNAME' => 'admin',
    'DEFAULT_PASSWORD' => 'GantiDenganPasswordKuat123!',

    // Cloudflare R2
    'R2_ACCOUNT_ID'        => 'your_account_id',
    'R2_BUCKET'            => 'your_bucket_name',
    'R2_ACCESS_KEY_ID'     => 'your_access_key_id',
    'R2_SECRET_ACCESS_KEY' => 'your_secret_access_key',
    'R2_ENDPOINT_BASE'     => 'https://your_account_id.r2.cloudflarestorage.com',

    // Opsional: sesuaikan quota dan batas upload
    'QUOTA_BYTES'      => 1073741824,  // 1 GB
    'MAX_UPLOAD_BYTES' => 104857600,   // 100 MB per file
];
```

> ⚠️ **Penting**: Password default (`DEFAULT_PASSWORD`) hanya digunakan saat seed pertama kali (saat tabel `users` masih kosong). Setelah itu, ubah password melalui menu **Pengaturan** di aplikasi. Password disimpan sebagai **bcrypt hash** di database.

### Langkah 3: Build Frontend

```bash
cd frontend
npm install
npm run build
```

Hasil build akan masuk ke `public/assets/app.js` dan `public/assets/app.css`.

### Langkah 4: Upload ke Hosting

Upload seluruh folder ke hosting. Pastikan **document root** mengarah ke folder `public/`.

```
hosting_root/
├── public/          ← Document Root
├── app/
├── config/
├── data/            ← Akan dibuat otomatis
└── frontend/        ← Opsional (tidak perlu di-upload ke production)
```

### Langkah 5: Set Permission

```bash
# Pastikan folder data bisa ditulis oleh PHP
chmod 755 data/
# Atau jika perlu:
chmod 775 data/
```

### Langkah 6: Konfigurasi R2 CORS

Di **Cloudflare Dashboard** → **R2** → **Bucket Settings** → **CORS Policy**, tambahkan:

```json
[
  {
    "AllowedOrigins": ["https://yourdomain.com"],
    "AllowedMethods": ["GET", "PUT", "HEAD", "DELETE"],
    "AllowedHeaders": ["*"],
    "ExposeHeaders": ["ETag", "Content-Length", "Content-Type"],
    "MaxAgeSeconds": 86400
  }
]
```

### Langkah 7: Akses Aplikasi

Buka `https://yourdomain.com/login` dan login dengan kredensial yang sudah dikonfigurasi.

---

## 🔧 Konfigurasi Lengkap

| Key | Default | Deskripsi |
|-----|---------|-----------|
| `APP_NAME` | `Syamsu Files` | Nama aplikasi (tampil di UI) |
| `BASE_PATH` | ` ` (kosong) | Isi `/subfolder` jika deploy di subfolder |
| `APP_URL` | — | URL lengkap domain tanpa trailing slash |
| `DEFAULT_USERNAME` | `admin` | Username awal (hanya untuk seed) |
| `DEFAULT_PASSWORD` | `admin123` | Password awal, akan di-hash bcrypt |
| `MAX_UPLOAD_BYTES` | `104857600` | Batas ukuran file (100 MB) |
| `QUOTA_BYTES` | `1073741824` | Quota total storage (1 GB) |
| `ALLOWED_MIME_PREFIXES` | `[image/, application/pdf, ...]` | Tipe MIME yang diizinkan |
| `BLOCKED_EXTENSIONS` | `[php, exe, ...]` | Ekstensi file yang diblokir |
| `R2_ACCOUNT_ID` | — | Cloudflare Account ID |
| `R2_BUCKET` | — | Nama bucket R2 |
| `R2_ACCESS_KEY_ID` | — | R2 API Token Access Key |
| `R2_SECRET_ACCESS_KEY` | — | R2 API Token Secret Key |
| `R2_ENDPOINT_BASE` | — | `https://{ACCOUNT_ID}.r2.cloudflarestorage.com` |
| `R2_REGION` | `auto` | Region R2 (selalu `auto`) |

---

## 🔒 Keamanan

### Password
- Password disimpan sebagai **bcrypt hash** menggunakan `password_hash()` PHP
- Verifikasi menggunakan `password_verify()` — timing-safe
- Password default **hanya digunakan saat seed pertama** (tabel users kosong)
- Ubah password melalui **Pengaturan** di aplikasi setelah login pertama

### Session & CSRF
- Session cookie: `httponly`, `samesite=Lax`, `secure` (jika HTTPS)
- CSRF token wajib untuk semua request state-changing (POST/DELETE)
- Token dikirim via header `X-CSRF-Token`

### Rate Limiting
- Login dibatasi **5 percobaan per 60 detik** per session
- Counter di-reset setelah login berhasil

### Upload Validation
- Validasi ekstensi file (block `.php`, `.exe`, dll)
- Validasi MIME type (whitelist prefix)
- Validasi ukuran file (batas dari config)
- Validasi quota storage
- Object key menggunakan UUID random (bukan nama file dari user)
- Nama tampilan di-sanitasi (hapus karakter berbahaya)

### Storage
- File disimpan di Cloudflare R2 dengan key random
- Database SQLite disimpan di folder `data/` (di luar document root)
- Presigned URL berlaku singkat (5 menit untuk upload/download)

---

## 📡 API Endpoints

### Auth
| Method | Endpoint | Deskripsi |
|--------|----------|-----------|
| `GET` | `/api/csrf` | Ambil CSRF token |
| `POST` | `/api/login` | Login |
| `POST` | `/api/logout` | Logout |
| `GET` | `/api/me` | Info user yang sedang login |
| `POST` | `/api/change-password` | Ganti password |

### Files
| Method | Endpoint | Deskripsi |
|--------|----------|-----------|
| `GET` | `/api/files` | Daftar file & folder (param: `folder_id`) |
| `POST` | `/api/files/init-upload` | Minta presigned PUT URL |
| `POST` | `/api/files/complete-upload` | Konfirmasi upload selesai |
| `POST` | `/api/files/{id}/rename` | Ganti nama file |
| `DELETE` | `/api/files/{id}` | Hapus file (soft/permanent) |
| `POST` | `/api/files/{id}/share` | Toggle berbagi publik |
| `POST` | `/api/files/{id}/restore` | Pulihkan dari trash |
| `POST` | `/api/files/{id}/move` | Pindahkan ke folder |
| `GET` | `/api/files/{id}/download-url` | Presigned download URL |
| `GET` | `/api/files/{id}/preview-url` | Presigned preview URL |

### Folders
| Method | Endpoint | Deskripsi |
|--------|----------|-----------|
| `POST` | `/api/folders` | Buat folder baru |
| `POST` | `/api/folders/{id}/rename` | Ganti nama folder |
| `DELETE` | `/api/folders/{id}` | Hapus folder (soft/permanent) |
| `POST` | `/api/folders/{id}/share` | Toggle berbagi publik |
| `POST` | `/api/folders/{id}/restore` | Pulihkan dari trash |
| `POST` | `/api/folders/{id}/move` | Pindahkan ke folder |

### Trash
| Method | Endpoint | Deskripsi |
|--------|----------|-----------|
| `GET` | `/api/trash` | Daftar file & folder di trash |
| `GET` | `/api/trash/count` | Jumlah item di trash |
| `POST` | `/api/trash/empty` | Kosongkan trash (hapus permanen semua) |

### Storage
| Method | Endpoint | Deskripsi |
|--------|----------|-----------|
| `GET` | `/api/usage` | Info penggunaan storage |

### Public
| Method | Endpoint | Deskripsi |
|--------|----------|-----------|
| `GET` | `/s/{token}` | Halaman download publik |
| `GET` | `/s/{token}/download` | Redirect ke presigned download URL |
| `GET` | `/s/{token}/download/{fileId}` | Download file dari shared folder |

---

## 🎨 Tema & Desain

Aplikasi menggunakan desain **Pastel Scrapbook** dengan dua tema:

### 🌸 Pastel Paper (Light)
- Background gradient lavender → pink
- Grid paper pattern
- Panel putih dengan border ungu
- Accent ungu + pink

### 🌙 Midnight Neon (Dark)
- Background gelap keunguan
- Grid tipis dengan opacity rendah
- Panel gelap dengan border ungu
- Accent neon ungu + pink

Toggle tema tersedia di **taskbar** (system tray) dan halaman login.

---

## 🛠️ Development

### Frontend Development Server

```bash
cd frontend
npm install
npm run dev
```

Frontend dev server akan berjalan di `http://localhost:5173`. Pastikan backend PHP juga berjalan (misalnya via XAMPP/Laragon dengan document root ke folder `public/`).

### Build untuk Production

```bash
cd frontend
npm run build
```

Output: `public/assets/app.js` dan `public/assets/app.css`

---

## 📋 FAQ

### Bagaimana mengubah password setelah login pertama?
Klik **📁 Mulai** → **Pengaturan** → masukkan password lama dan password baru → **Simpan**.

### Bagaimana jika lupa password?
Hapus file `data/sfiles.sqlite`, lalu akses kembali aplikasi. Database akan dibuat ulang dengan password default dari `config/config.php`.

### Apakah bisa multi-user?
Tidak. SFiles dirancang sebagai **penyimpanan pribadi single-user**. Hanya ada satu akun yang bisa login.

### Apakah file aman?
File disimpan di Cloudflare R2 dengan presigned URL berdurasi pendek. Database SQLite berada di luar document root. Password di-hash dengan bcrypt.

### Bagaimana menambah quota?
Ubah nilai `QUOTA_BYTES` di `config/config.php`. Contoh: `5368709120` untuk 5 GB.

### Apakah bisa deploy di subfolder?
Ya. Set `BASE_PATH` di config. Contoh: jika URL `https://domain.com/files`, set `BASE_PATH` = `/files`.

---

## 📄 Lisensi

MIT License — Silakan gunakan dan modifikasi sesuai kebutuhan.

---

<div align="center">

**✦ Syamsu Files ✦**

Dibuat dengan ❤️ menggunakan PHP, React, dan Cloudflare R2

</div>
