# Shortlink Features Documentation

**Version:** 3.0.0
**Date:** 2025-12-26
**Status:** ✅ Production Ready

---

## 📋 Table of Contents

1. [Overview](#overview)
2. [Features](#features)
3. [Random Subdomain System](#random-subdomain-system)
4. [Domain Selection](#domain-selection)
5. [Bulk Creation](#bulk-creation)
6. [Dashboard UI](#dashboard-ui)
7. [Database Schema](#database-schema)
8. [API Endpoints](#api-endpoints)
9. [Usage Examples](#usage-examples)
10. [Migration Guide](#migration-guide)

---

## Overview

Sistem shortlink dengan fitur lengkap:
- ✅ Random subdomain generation (`xy7k2m.example.com/slug`)
- ✅ Domain selection (Global/Specific)
- ✅ Bulk creation (1-25 links at once)
- ✅ Open Graph meta tags (Title, Description, Image)
- ✅ Shim page support
- ✅ Copy to clipboard functionality

---

## Features

### 1. Random Subdomain System

**Format:** `{random}.{domain}/{slug}`
**Example:** `xy7k2m.example.com/promo`

**Character Set:**
- Lowercase letters: `a-z` (excluding confusing chars: `l`, `o`)
- Numbers: `2-9` (excluding confusing chars: `0`, `1`)
- Length: 6 characters
- Collision avoidance: Retry up to 10 times

**Database:**
```sql
ALTER TABLE shortlinks ADD COLUMN subdomain VARCHAR(50) NULL;
ALTER TABLE shortlinks ADD UNIQUE INDEX uk_subdomain_slug (subdomain, slug);
```

---

### 2. Domain Selection

**Modes:**

#### A. Global Domain (Random)
```
use_global_domain = 1
domain_id = NULL
```

System randomly picks domain on EACH redirect:
- First click: `xy7k2m.example.com/promo`
- Second click: `ab4de7.example02.com/promo` (different domain)

**Benefits:**
- Anti-block mechanism
- Load distribution
- Domain rotation

#### B. Specific Domain
```
use_global_domain = 0
domain_id = 3
```

Always uses selected domain:
- All clicks: `xy7k2m.example02.com/promo` (same domain)

**Benefits:**
- Predictable URLs
- Brand consistency
- Analytics per domain

**Database:**
```sql
ALTER TABLE shortlinks ADD COLUMN domain_id INT UNSIGNED NULL;
ALTER TABLE shortlinks ADD COLUMN use_global_domain TINYINT(1) DEFAULT 0;
ALTER TABLE shortlinks ADD FOREIGN KEY fk_shortlink_domain (domain_id)
    REFERENCES domains(id) ON DELETE SET NULL;
```

---

### 3. Bulk Creation

**Limits:**
- Minimum: 1 shortlink
- Maximum: 25 shortlinks

**Process:**
1. User inputs base data (URL, Title, Description, Image)
2. System generates unique slug for each shortlink
3. System generates unique subdomain for each
4. All links share same OG meta tags
5. All links use same domain selection mode

**Output:**
```
https://xy7k2m.example.com/abc123
https://de4fg7.example.com/xyz789
https://mn8pq2.example.com/qwe456
```

**Transaction:**
- All-or-nothing (rollback on error)
- Collision detection per link
- Unique constraint validation

---

### 4. Dashboard UI

**Form Fields:**

#### Required:
- **Destination URL:** Target URL for redirect

#### Optional:
- **Title:** Open Graph title (max 255 chars)
- **Description:** Open Graph description (textarea)
- **Image URL:** Open Graph image (1200x630px recommended)

#### Settings:
- **Domain Dropdown:**
  ```
  🌐 Global Domain (Random)
  ──────────────────────
  example.com
  example01.com
  example02.com
  ```

- **Shim Link Checkbox:**
  - Enable intermediate page before redirect
  - Better for Facebook/WhatsApp
  - Countdown timer + analytics

- **Bulk Limit:**
  - Number input (1-25)
  - Validation on both frontend and backend

#### Output:
- **Textarea:** Read-only, copy-friendly
- **Copy Button:** Position absolute, top-right
- **Link Count:** Display number of generated links
- **Format Info:** Show URL structure

---

## Database Schema

### Migration 008 - Random Subdomain

```sql
-- File: database-migrations/008_add_shortlink_subdomain.sql

ALTER TABLE shortlinks
ADD COLUMN subdomain VARCHAR(50) NULL AFTER slug,
ADD UNIQUE INDEX uk_subdomain_slug (subdomain, slug),
ADD INDEX idx_subdomain (subdomain);
```

### Migration 009 - Domain Selection

```sql
-- File: database-migrations/009_add_shortlink_domain_id.sql

ALTER TABLE shortlinks
ADD COLUMN domain_id INT UNSIGNED NULL AFTER subdomain,
ADD COLUMN use_global_domain TINYINT(1) NOT NULL DEFAULT 0 AFTER domain_id,
ADD INDEX idx_domain_id (domain_id),
ADD FOREIGN KEY fk_shortlink_domain (domain_id)
    REFERENCES domains(id)
    ON DELETE SET NULL;
```

### Complete Schema

```sql
CREATE TABLE shortlinks (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    slug VARCHAR(100) NOT NULL,
    subdomain VARCHAR(50) NULL,
    domain_id INT UNSIGNED NULL,
    use_global_domain TINYINT(1) NOT NULL DEFAULT 0,
    destination_url TEXT NOT NULL,

    -- Open Graph Meta Tags
    og_title VARCHAR(255) NULL,
    og_description TEXT NULL,
    og_image VARCHAR(500) NULL,
    og_type VARCHAR(50) DEFAULT 'website',
    fb_app_id VARCHAR(50) NULL,

    -- Additional Meta
    meta_keywords VARCHAR(500) NULL,
    meta_author VARCHAR(255) NULL,

    -- Status & Expiry
    status ENUM('active', 'inactive', 'expired') DEFAULT 'active',
    expires_at TIMESTAMP NULL,

    -- Timestamps
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    -- Indexes
    UNIQUE KEY uk_slug (slug),
    UNIQUE KEY uk_subdomain_slug (subdomain, slug),
    INDEX idx_subdomain (subdomain),
    INDEX idx_domain_id (domain_id),
    INDEX idx_status (status),
    INDEX idx_created (created_at),

    -- Foreign Keys
    FOREIGN KEY fk_shortlink_domain (domain_id)
        REFERENCES domains(id)
        ON DELETE SET NULL

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

---

## API Endpoints

### 1. Create Bulk Shortlinks

**File:** `public/ajax/create-bulk-shortlinks.php`

**Method:** `POST`

**Request:**
```javascript
{
    destination_url: "https://example.com/page",
    og_title: "Page Title",
    og_description: "Page description",
    og_image: "https://example.com/image.jpg",
    domain_selection: "global" | 3, // "global" or domain ID
    enable_shim: 1 | 0,
    count: 5, // 1-25
    csrf_token: "..."
}
```

**Response:**
```javascript
{
    success: true,
    message: "Created 5 shortlinks successfully",
    shortlinks: [
        {
            id: 123,
            slug: "abc123",
            subdomain: "xy7k2m",
            url: "https://xy7k2m.example.com/abc123"
        },
        // ...
    ],
    count: 5,
    csrf_token: "new-token-for-next-request"
}
```

### 2. Get Domains

**File:** `public/ajax/get-domains.php`

**Method:** `GET`

**Request:**
```
GET /ajax/get-domains.php?status=active&limit=100
```

**Response:**
```javascript
{
    success: true,
    data: {
        domains: [
            {
                id: 1,
                domain: "example.com",
                subdomain: "example",
                status: "active"
            },
            {
                id: 2,
                domain: "example01.com",
                subdomain: "example01",
                status: "active"
            }
        ],
        total: 2
    }
}
```

---

## Usage Examples

### Example 1: Create Single Shortlink (Global Domain)

**Input:**
```javascript
{
    destination_url: "https://mysite.com/product",
    og_title: "Amazing Product",
    og_description: "Check out this product",
    domain_selection: "global",
    count: 1
}
```

**Output:**
```
https://xy7k2m.example.com/abc123
```

**Behavior:**
- First click → redirects via `example.com`
- Second click → might redirect via `example01.com` (random)

---

### Example 2: Create Bulk Links (Specific Domain)

**Input:**
```javascript
{
    destination_url: "https://mysite.com/promo",
    og_title: "Limited Offer",
    domain_selection: 3, // example02.com
    count: 10
}
```

**Output:**
```
https://ab3def.example02.com/mn7pq2
https://gh4ijk.example02.com/rs8tuv
https://wx5yz6.example02.com/ab9cde
... (10 links total)
```

**Behavior:**
- All links always use `example02.com`
- Each link has unique subdomain
- Each link has unique slug

---

### Example 3: With Shim Page

**Input:**
```javascript
{
    destination_url: "https://mysite.com/facebook-offer",
    og_title: "Special Offer",
    og_image: "https://mysite.com/promo.jpg",
    enable_shim: 1,
    domain_selection: "global",
    count: 5
}
```

**Flow:**
1. User clicks: `https://xy7k2m.example.com/abc123`
2. Redirect to: `https://example.com/shim.php?slug=abc123&dest=BASE64`
3. Shim page shows:
   - Countdown (5 seconds)
   - Custom message
   - Manual continue button
   - OG meta tags for preview
4. After countdown → final destination

---

## Migration Guide

### Step 1: Backup Database

```bash
mysqldump -u user -p database_name > backup_$(date +%Y%m%d).sql
```

### Step 2: Run Migrations

```bash
cd E:\.rounting\dashboard

# Migration 008
mysql -u user -p database_name < database-migrations/008_add_shortlink_subdomain.sql

# Migration 009
mysql -u user -p database_name < database-migrations/009_add_shortlink_domain_id.sql
```

### Step 3: Verify Schema

```sql
-- Check subdomain column
SHOW COLUMNS FROM shortlinks LIKE 'subdomain';

-- Check domain_id column
SHOW COLUMNS FROM shortlinks LIKE 'domain_id';

-- Check indexes
SHOW INDEX FROM shortlinks WHERE Key_name = 'uk_subdomain_slug';
```

### Step 4: Test Functionality

1. **Test Single Creation:**
   - Create 1 shortlink with Global domain
   - Verify URL format: `{subdomain}.{domain}/{slug}`

2. **Test Bulk Creation:**
   - Create 5 shortlinks
   - Verify all unique
   - Verify all accessible

3. **Test Domain Selection:**
   - Create link with Global domain
   - Create link with Specific domain
   - Verify routing works correctly

4. **Test Shim Page:**
   - Enable shim checkbox
   - Click generated link
   - Verify intermediate page shows
   - Verify countdown works
   - Verify final redirect works

---

## Frontend Integration

### Include Component in Dashboard

**File:** `public/index.php`

```php
<!-- In Shortlinks Tab -->
<div class="tab-pane fade" id="shortlinks">
    <?php include __DIR__ . '/components/shortlink-form.html'; ?>
</div>
```

### Required Meta Tag (CSRF)

```html
<meta name="csrf-token" content="<?= $_SESSION['csrf_token'] ?? '' ?>">
```

### Required Bootstrap Icons

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
```

---

## Security Considerations

### 1. Input Validation

✅ **Backend:**
- URL format validation (`FILTER_VALIDATE_URL`)
- Length limits (title: 255, description: unlimited)
- Bulk count limit (1-25)
- Domain ID validation (must exist in database)

✅ **Frontend:**
- HTML5 validation attributes
- Type constraints (`url`, `number`)
- Min/max constraints

### 2. CSRF Protection

✅ **Token Generation:**
```php
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
```

✅ **Token Validation:**
```php
hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])
```

✅ **Token Rotation:**
- New token generated after each successful request
- Prevents token reuse attacks

### 3. XSS Prevention

✅ **Output Escaping:**
```php
htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8')
```

✅ **CSP Headers:**
```
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'
```

---

## Performance Optimization

### 1. Database Indexes

```sql
-- Fast slug lookup
INDEX idx_slug (slug)

-- Fast subdomain + slug lookup (unique)
UNIQUE INDEX uk_subdomain_slug (subdomain, slug)

-- Fast subdomain lookup
INDEX idx_subdomain (subdomain)

-- Fast domain lookup
INDEX idx_domain_id (domain_id)
```

### 2. Bulk Insert Transaction

```php
$this->repository->beginTransaction();
try {
    foreach ($items as $item) {
        $this->repository->create($item);
    }
    $this->repository->commit();
} catch (\Exception $e) {
    $this->repository->rollback();
}
```

### 3. Collision Avoidance

```php
// Retry up to 10 times for unique subdomain
for ($i = 0; $i < 10; $i++) {
    $subdomain = generateRandom(6);
    if (!exists($subdomain)) return $subdomain;
}
```

---

## Troubleshooting

### Issue: Subdomain collision errors

**Symptom:** "Failed to generate unique subdomain"

**Solution:**
```sql
-- Check collision rate
SELECT COUNT(DISTINCT subdomain) / COUNT(*) as collision_rate
FROM shortlinks
WHERE subdomain IS NOT NULL;

-- If > 0.8 (80%), increase subdomain length from 6 to 8
```

### Issue: Domain dropdown empty

**Symptom:** Only shows "Global Domain"

**Solution:**
```bash
# Check domains exist
SELECT * FROM domains WHERE status = 'active';

# Check AJAX endpoint
curl http://localhost/ajax/get-domains.php
```

### Issue: Bulk creation fails

**Symptom:** "Failed to create bulk shortlinks"

**Solution:**
```bash
# Check error log
tail -f /var/log/php/error.log

# Check transaction rollback
# Verify database connection timeout
# Increase max_execution_time if needed
```

---

## Changelog

### Version 3.0.0 (2025-12-26)

**Added:**
- ✅ Random subdomain generation system
- ✅ Domain selection (Global/Specific)
- ✅ Bulk creation (1-25 links)
- ✅ Enhanced dashboard UI with all fields
- ✅ Copy to clipboard functionality
- ✅ Open Graph meta tags support

**Database:**
- Migration 008: Add subdomain column
- Migration 009: Add domain selection

**Files Created:**
- `src/Service/SubdomainGenerator.php`
- `public/ajax/create-bulk-shortlinks.php`
- `public/components/shortlink-form.html`
- `database-migrations/008_add_shortlink_subdomain.sql`
- `database-migrations/009_add_shortlink_domain_id.sql`

**Files Updated:**
- `src/Repository/ShortlinkRepository.php`
- `src/Service/ShortlinkService.php`
- `redirect/s.php` (subdomain routing)
- `bootstrap/init.php` (inject SubdomainGenerator)

---

**For support, see:** `dashboard/CLAUDE.md`

**Last Updated:** 2025-12-26
