JMG Central Storage
Storage terpusat untuk semua aplikasi Java Media Grup. Upload sekali, akses dari mana saja — dengan isolasi per client dan siap upgrade ke S3.
Memulai
Ikuti langkah berikut untuk mulai menggunakan JMG Storage dari aplikasi Anda.
Minta akses ke admin
Hubungi admin JMG untuk mendaftarkan aplikasi Anda sebagai client baru. Admin akan membuatkan token API khusus untuk aplikasi Anda.
Simpan token di .env
Pasang token yang diberikan admin ke file .env aplikasi Anda. Jangan commit token ke repository.
Kirim request
Gunakan token sebagai Bearer Token di header Authorization setiap request ke API.
Autentikasi
Semua endpoint API membutuhkan header Authorization dengan Bearer Token.
CENTRAL_STORAGE_URL=https://storage.jmg.net.id CENTRAL_STORAGE_TOKEN=sm_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Upload File
Upload file gambar dengan multipart/form-data.
/api/v1/files
| Field | Tipe | Keterangan |
|---|---|---|
| file | File *wajib | jpg, jpeg, png, webp, gif. Maks sesuai konfigurasi client. |
| visibility | String | public (default) atau private |
| collection | String | Grup file. Huruf kecil, angka, _-. Contoh: avatars, posts |
curl -X POST 'https://storage.jmg.net.id/api/v1/files' \ -H 'Authorization: Bearer $CENTRAL_STORAGE_TOKEN' \ -H 'Accept: application/json' \ -F 'file=@/path/to/photo.jpg' \ -F 'visibility=public' \ -F 'collection=avatars'
{
"data": {
"id": "019756ab-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"visibility": "public",
"collection": "avatars",
"original_name": "photo.jpg",
"mime_type": "image/jpeg",
"size_bytes": 124550,
"checksum_sha256": "a3f1...",
"metadata": { "width": 800, "height": 800 },
"url": "https://storage.jmg.net.id/files/019756ab-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"created_at": "2026-06-29T10:00:00+00:00"
}
}
List & Get File
Ambil daftar file milik client (paginated) atau metadata satu file.
/api/v1/files
Query params: collection, visibility, per_page (maks 100, default 20), page
curl 'https://storage.jmg.net.id/api/v1/files?collection=avatars&per_page=10' \ -H 'Authorization: Bearer $CENTRAL_STORAGE_TOKEN'
/api/v1/files/{uuid}
curl 'https://storage.jmg.net.id/api/v1/files/019756ab-xxxx-xxxx-xxxx-xxxxxxxxxxxx' \ -H 'Authorization: Bearer $CENTRAL_STORAGE_TOKEN'
Hapus File
/api/v1/files/{uuid}
curl -X DELETE 'https://storage.jmg.net.id/api/v1/files/019756ab-xxxx-xxxx-xxxx-xxxxxxxxxxxx' \ -H 'Authorization: Bearer $CENTRAL_STORAGE_TOKEN'
Response: 204 No Content. File dihapus dari disk dan metadata di-soft-delete.
File Private & Signed URL
File private tidak punya URL permanen. Gunakan endpoint ini untuk membuat signed URL yang valid selama 15 menit.
/api/v1/files/{uuid}/temporary-url
curl -X POST 'https://storage.jmg.net.id/api/v1/files/019756ab-xxxx/temporary-url' \ -H 'Authorization: Bearer $CENTRAL_STORAGE_TOKEN'
{
"id": "019756ab-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"url": "https://storage.jmg.net.id/private-files/019756ab-xxxx?signature=...",
"expires_in_minutes": 15
}
Berikan URL ini ke browser pengguna — URL otomatis kedaluwarsa setelah 15 menit.
Integrasi Laravel
Buat service class di aplikasi pemanggil untuk enkapsulasi komunikasi ke storage API.
<?php
namespace App\Services;
use Illuminate\Http\Client\Response;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Http;
class CentralStorageClient
{
private string $baseUrl;
private string $token;
public function __construct()
{
$this->baseUrl = rtrim(config('services.central_storage.url'), '/');
$this->token = config('services.central_storage.token');
}
/** Upload file, kembalikan array metadata dari storage. */
public function upload(
UploadedFile $file,
string $visibility = 'public',
string $collection = 'images'
): array {
$response = Http::withToken($this->token)
->attach('file', $file->getContent(), $file->getClientOriginalName())
->post("{$this->baseUrl}/api/v1/files", [
'visibility' => $visibility,
'collection' => $collection,
]);
$response->throw(); // lempar exception jika gagal
return $response->json('data');
}
/** Ambil metadata satu file. */
public function get(string $uuid): array
{
return Http::withToken($this->token)
->get("{$this->baseUrl}/api/v1/files/{$uuid}")
->throw()
->json('data');
}
/** Minta signed URL untuk file private (valid 15 menit). */
public function temporaryUrl(string $uuid): string
{
return Http::withToken($this->token)
->post("{$this->baseUrl}/api/v1/files/{$uuid}/temporary-url")
->throw()
->json('url');
}
/** Hapus file. */
public function delete(string $uuid): void
{
Http::withToken($this->token)
->delete("{$this->baseUrl}/api/v1/files/{$uuid}")
->throw();
}
}
'central_storage' => [
'url' => env('CENTRAL_STORAGE_URL'),
'token' => env('CENTRAL_STORAGE_TOKEN'),
],
# .env aplikasi pemanggil CENTRAL_STORAGE_URL=https://storage.jmg.net.id CENTRAL_STORAGE_TOKEN=sm_live_TOKEN_ANDA
public function store(Request $request, CentralStorageClient $storage): JsonResponse
{
$request->validate(['avatar' => ['required', 'image', 'max:5120']]);
// Upload ke storage
$media = $storage->upload(
file: $request->file('avatar'),
visibility: 'public',
collection: 'avatars'
);
// Simpan UUID dan URL ke database
$user->update([
'avatar_storage_id' => $media['id'],
'avatar_url' => $media['url'],
]);
return response()->json($media, 201);
}
JavaScript (dari Backend)
Jangan panggil API storage langsung dari browser — token akan bocor. Selalu lewatkan backend aplikasi Anda.
const formData = new FormData();
formData.append('file', fs.createReadStream('/path/to/photo.jpg'));
formData.append('visibility', 'public');
formData.append('collection', 'avatars');
const response = await fetch(`${process.env.CENTRAL_STORAGE_URL}/api/v1/files`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.CENTRAL_STORAGE_TOKEN}`,
'Accept': 'application/json',
...formData.getHeaders(),
},
body: formData,
});
const { data } = await response.json();
console.log(data.url); // URL file public
cURL — Referensi Lengkap
curl -X POST 'https://storage.jmg.net.id/api/v1/files' \ -H 'Authorization: Bearer $TOKEN' \ -F 'file=@photo.jpg' \ -F 'visibility=public' \ -F 'collection=avatars'
curl -X POST 'https://storage.jmg.net.id/api/v1/files' \ -H 'Authorization: Bearer $TOKEN' \ -F 'file=@dokumen.png' \ -F 'visibility=private' \ -F 'collection=documents'
curl 'https://storage.jmg.net.id/api/v1/files' \ -H 'Authorization: Bearer $TOKEN'
curl 'https://storage.jmg.net.id/api/v1/files?collection=avatars&visibility=public&per_page=20&page=2' \ -H 'Authorization: Bearer $TOKEN'
curl 'https://storage.jmg.net.id/api/v1/files/{uuid}' \
-H 'Authorization: Bearer $TOKEN'
curl -X POST 'https://storage.jmg.net.id/api/v1/files/{uuid}/temporary-url' \
-H 'Authorization: Bearer $TOKEN'
curl -X DELETE 'https://storage.jmg.net.id/api/v1/files/{uuid}' \
-H 'Authorization: Bearer $TOKEN'
Upgrade ke S3-Compatible Storage
Ketika trafik bertambah, Anda bisa pindah ke object storage (AWS S3, Cloudflare R2, MinIO) tanpa mengubah kontrak API — cukup ubah konfigurasi .env.
-
1
Install Flysystem S3 adapter
composer require league/flysystem-aws-s3-v3
-
2
Update .env storage server
.env (storage server) bash# Ganti disk ke S3 MEDIA_PUBLIC_DISK=media_public_s3 MEDIA_PRIVATE_DISK=media_private_s3 MEDIA_PUBLIC_DELIVERY=disk # Kredensial S3 / R2 / MinIO AWS_ACCESS_KEY_ID=your-key AWS_SECRET_ACCESS_KEY=your-secret AWS_DEFAULT_REGION=auto AWS_BUCKET=jmg-media-public AWS_PRIVATE_BUCKET=jmg-media-private AWS_ENDPOINT=https://xxxx.r2.cloudflarestorage.com AWS_URL=https://cdn.jmg.net.id
-
3
Clear cache & test
php artisan config:cache php artisan route:cache
File baru langsung masuk ke S3. File lama di local disk tetap bisa dilayani karena kolom
diskdi database menyimpan lokasi per-file. -
4
Migrasi file lama (opsional)
Buat job Laravel untuk copy file dari local disk ke S3 secara bertahap, update kolom
disksetelah berhasil.
Error Codes
| Status | Penyebab |
|---|---|
| 401 | Token tidak ada, salah, atau sudah kedaluwarsa |
| 403 | Origin browser tidak diizinkan (jika ada header Origin) |
| 404 | File tidak ditemukan atau milik client lain |
| 422 | Validasi gagal — format file tidak didukung, ukuran melebihi batas, atau field salah |
| 429 | Rate limit terlampaui (upload: 30/menit, API: 120/menit per client) |
| 500 | File gagal disimpan ke disk — cek quota storage |