# API & Routes - Database

> Hướng dẫn cho AI biết cách sử dụng database

## Prompt Content

```
# APPIFIO App Storage API - Complete AI Developer Guide

## 🚫 CRITICAL: THIS IS USAGE DOCUMENTATION

**AppStorageClient is PRE-BUILT and AUTO-INJECTED into your application.**

### Your Role:
- ✅ **USE** existing methods
- ✅ **CALL** `appifio_client.appifio_*()` functions
- ❌ **NEVER** define `class AppStorageClient`
- ❌ **NEVER** rewrite `appifio_*` methods

---

## 🌐 GLOBAL VARIABLES & FUNCTIONS (Auto-Injected by System)

### 1. Global Configuration Object

The system automatically injects this into every page:

```javascript
window.appifio = {
    global: {
        apps: {
            url_name: "test-blog",                              // Current app URL name
            api_url: "https://appifio.com/l/link/app_storage",  // API endpoint
            site_url: "https://appifio.com/",                   // Base site URL
            api_key: "encoded_api_key_here"                     // Encoded API key
        }
    }
}
```

**Usage:**
```javascript
// Get URL name
const urlName = window.appifio.global.apps.url_name;

// Get site URL
const siteUrl = window.appifio.global.apps.site_url;

// Get API URL
const apiUrl = window.appifio.global.apps.api_url;
```

### 2. AppStorage Client Instance

The system creates `appifio_client` as a getter property:

```javascript
// ✅ CORRECT - appifio_client is always available (never undefined)
if (appifio_client && appifio_client.appifio_readFile) {
    const result = await appifio_client.appifio_readFile('blog.json');
}

// ❌ WRONG - Don't check typeof appifio_client === 'undefined'
// It's a getter property, always exists (returns proxyClient if not ready)
```

**Important:** `appifio_client` may return a proxy client that queues calls until the real client is ready. Always check for method existence, not the variable itself.

### 3. Global Helper Functions

Available from `biolink_handler.php`:

```javascript
// Wait for AppStorage client to be ready
await window.waitForAppStorage();

// Callback when AppStorage is ready
window.onAppStorageReady((client) => {
    console.log('Client ready:', client);
});

// Get client instance (may return proxy)
const client = window.getAppStorageClient();

// Get client with callback
await window.withClient(async (client) => {
    await client.appifio_readFile('blog.json');
});
```

**Recommended Pattern:**
```javascript
// Wait for client to be ready
try {
    if (typeof window.waitForAppStorage === 'function') {
        await window.waitForAppStorage();
    } else if (typeof window.onAppStorageReady === 'function') {
        await new Promise((resolve) => {
            window.onAppStorageReady(() => resolve());
        });
    } else {
        // Fallback: wait for appifio_client methods
        let retries = 0;
        while ((!appifio_client || !appifio_client.appifio_readFile) && retries < 50) {
            await new Promise(resolve => setTimeout(resolve, 100));
            retries++;
        }
    }
} catch (error) {
    console.warn('Error waiting for AppStorage:', error);
}
```

---

## 🔗 ABSOLUTE URL HANDLING

### ⚠️ CRITICAL: Base Tag Issue

The system uses a `<base>` tag that affects relative URLs. **Always use absolute URLs** for navigation links.

### Helper Function: Get Absolute URL

```javascript
function getAbsoluteUrl(path) {
    // Get site URL from global config
    let siteUrl = window.location.origin;
    if (window.appifio && window.appifio.global && window.appifio.global.apps && window.appifio.global.apps.site_url) {
        siteUrl = window.appifio.global.apps.site_url.replace(/\/$/, ''); // Remove trailing slash
    }
    
    // Get URL name
    let urlName = '';
    if (window.appifio && window.appifio.global && window.appifio.global.apps && window.appifio.global.apps.url_name) {
        urlName = window.appifio.global.apps.url_name;
    } else {
        // Fallback: extract from current URL
        const pathParts = window.location.pathname.split('/').filter(p => p);
        urlName = pathParts[0] || '';
    }
    
    // Build absolute URL
    return siteUrl + '/' + urlName + '/' + path.replace(/^\//, '');
}

// Usage examples:
const homeUrl = getAbsoluteUrl('');           // https://appifio.com/test-blog/
const blogUrl = getAbsoluteUrl('my-blog');   // https://appifio.com/test-blog/my-blog
const tagUrl = getAbsoluteUrl('tag/javascript'); // https://appifio.com/test-blog/tag/javascript
```

### Helper Function: Get URL Name

```javascript
function getUrlName() {
    if (window.appifio && window.appifio.global && window.appifio.global.apps && window.appifio.global.apps.url_name) {
        return window.appifio.global.apps.url_name;
    }
    // Fallback: extract from URL
    const pathParts = window.location.pathname.split('/').filter(p => p);
    return pathParts[0] || '';
}
```

### Example: Back Link with Absolute URL

```javascript
function updateBackLink() {
    const backLink = document.getElementById('back-link');
    if (backLink) {
        const urlName = getUrlName();
        let siteUrl = window.location.origin;
        if (window.appifio && window.appifio.global && window.appifio.global.apps && window.appifio.global.apps.site_url) {
            siteUrl = window.appifio.global.apps.site_url.replace(/\/$/, '');
        }
        // Use absolute path to avoid base tag issues
        backLink.href = siteUrl + '/' + urlName + '/';
    }
}
```

---

## ⚠️ CRITICAL CONCEPT 1: File Not Found is NORMAL

### Understanding First-Time Usage

**When reading a file that doesn't exist yet, the API returns `success: false`.**

This is **NORMAL** for first-time usage. You must handle this gracefully.

### ✅ CORRECT PATTERN: Always Initialize with Default Structure

```javascript
// ✅ CORRECT - Always start with default structure
async function loadBlogs() {
    // 1. Initialize with default structure FIRST
    let blogIndex = {
        blogs: {},
        metadata: {
            total: 0,
            last_updated: ''
        }
    }
    
    // 2. Try to read existing file
    try {
        if (appifio_client && appifio_client.appifio_readFile) {
            const result = await appifio_client.appifio_readFile('blog.json')
            
            // 3. Only use data if read was successful
            if (result && result.success) {
                blogIndex = JSON.parse(result.data.content)
            } else {
                // File doesn't exist yet - this is OK!
                console.log('blog.json not found, using empty structure')
            }
        }
    } catch (error) {
        // Also OK - file doesn't exist
        console.log('blog.json not found:', error.message)
    }
    
    // 4. Continue with blogIndex (empty or loaded)
    return blogIndex
}
```

### ❌ WRONG PATTERN: Don't Assume File Exists

```javascript
// ❌ WRONG - Will crash if file doesn't exist
const result = await appifio_client.appifio_readFile('blog.json')
const blogIndex = JSON.parse(result.data.content) // CRASH if result.success === false!
```

---

## ⚠️ CRITICAL CONCEPT 2: Routes are MANDATORY

### Understanding the Route System

**IMPORTANT: The system ONLY reads `index.html` by default!**

- ✅ URL: `/urlname/` → Reads: `index.html` (automatic)
- ❌ URL: `/urlname/blog-detail` → **NOTHING** (no file served)
- ✅ URL: `/urlname/blog-detail` → Reads: `blog/detail.html` (IF route exists)

### Why Routes are Required

**Without routes, only `index.html` works. ALL other URLs return 404!**

To make ANY other page work, you MUST:
1. Create the HTML file in filesystem
2. Add route mapping to `routes.json`

### Route System Architecture

```
User visits: /urlname/my-blog-post
    ↓
System checks: routes.json
    ↓
Finds: "my-blog-post" → { "file": "blog/detail.html" }
    ↓
Serves: blog/detail.html
    ↓
blog/detail.html reads data from blog.json
    ↓
Displays blog content
```

### ⚠️ CRITICAL: Nested Route Paths

**Route paths can contain slashes for nested routes:**

```javascript
// ✅ CORRECT - Nested routes work
await appifio_client.appifio_addRoute('tag/javascript', 'blog/tag.html', 'tag');
await appifio_client.appifio_addRoute('category/tech', 'blog/category.html', 'category');

// URL: /urlname/tag/javascript → Loads blog/tag.html
// URL: /urlname/category/tech → Loads blog/category.html
```

**The routing system extracts the full path after `urlname`:**

- URL: `/test-blog/tag/3123`
- Route path extracted: `tag/3123`
- Matches route in `routes.json`: `"tag/3123"`

### routes.json Structure

```json
{
  "routes": {
    "my-blog-post": {
      "file": "blog/detail.html",
      "type": "blog",
      "enabled": true
    },
    "tag/javascript": {
      "file": "blog/tag.html",
      "type": "tag",
      "enabled": true
    },
    "category/tech": {
      "file": "blog/category.html",
      "type": "category",
      "enabled": true
    }
  },
  "fallback": "index.html"
}
```

### ✅ MANDATORY: Always Create Routes

**Every time you create content, you MUST add a route!**

```javascript
// ❌ WRONG - No route created, URL won't work
await appifio_client.appifio_writeFile('blog.json', blogData)
// User visits /urlname/my-blog → 404 ERROR!

// ✅ CORRECT - Route created, URL works
await appifio_client.appifio_writeFile('blog.json', blogData)
await appifio_client.appifio_addRoute('my-blog', 'blog/detail.html', 'blog')
// User visits /urlname/my-blog → ✅ Works!
```

---

## 📚 AVAILABLE API METHODS

### A. TempDB Storage Operations (Data Files)

Store JSON data files like `blog.json`, `settings.json`:

```javascript
// Read file
const result = await appifio_client.appifio_readFile('blog.json')
if (result && result.success) {
    const data = JSON.parse(result.data.content)
}

// Write file (creates if doesn't exist, overwrites if exists)
const saveResult = await appifio_client.appifio_writeFile('blog.json', JSON.stringify(data, null, 2))
if (!saveResult.success) {
    console.error('Save failed:', saveResult.message)
}

// Delete file
await appifio_client.appifio_deleteFile('blog.json')

// List files
const files = await appifio_client.appifio_listFiles()

// File exists check
const exists = await appifio_client.appifio_fileExists('blog.json')

// Get file info
const info = await appifio_client.appifio_getFileInfo('blog.json')
```

### B. Filesystem Operations (HTML, Routes)

Store HTML files, routes.json in filesystem:

```javascript
// Read from filesystem
const result = await appifio_client.appifio_readFsFile('routes.json')
if (result && result.success) {
    const routes = JSON.parse(result.data.content)
}

// Write to filesystem
await appifio_client.appifio_writeFsFile('routes.json', JSON.stringify(routes, null, 2))

// Delete from filesystem
await appifio_client.appifio_deleteFsFile('file.html')

// List filesystem files
const fsFiles = await appifio_client.appifio_listFsFiles()

// Get filesystem file info
const fsInfo = await appifio_client.appifio_getFsFileInfo('routes.json')
```

### C. Route Management (MANDATORY)

```javascript
// Add route (REQUIRED for every content)
const routeResult = await appifio_client.appifio_addRoute(
    'my-blog-post',        // Route path (can be nested: 'tag/javascript')
    'blog/detail.html',    // Target HTML file
    'blog'                 // Route type (optional, for categorization)
)

if (!routeResult.success) {
    console.warn('Failed to add route:', routeResult.message)
}

// Remove route (MANDATORY when deleting content)
const removeResult = await appifio_client.appifio_removeRoute('my-blog-post')

// Get all routes
const routesResult = await appifio_client.appifio_getRoutes()
if (routesResult.success && routesResult.data) {
    const routes = routesResult.data.routes || {}
    console.log('All routes:', routes)
}
```

### D. Helper Functions

```javascript
// Generate slug (supports Vietnamese)
const slug = appifio_client.appifio_generateSlug("Bài viết về AI")
// Returns: "bai-viet-ve-ai"

// Format datetime
const formatted = appifio_client.appifio_formatDateTime('2025-12-27 15:59:10', 'date')
// Returns: "27 Dec 2025"

// Format file size
const size = appifio_client.appifio_formatFileSize(1024)
// Returns: "1 KB"

// Get route slug from current URL
const currentSlug = appifio_client.appifio_getRouteSlug()
// Returns: "my-blog-post" for URL /urlname/my-blog-post

// Parse query parameters
const params = appifio_client.appifio_getQueryParams()
// Returns: { id: "123", page: "2" } for URL ?id=123&page=2
```

### E. Upload Operations

```javascript
// Upload image
const fileInput = document.getElementById('imageInput')
const uploadResult = await appifio_client.appifio_uploadImage(fileInput.files[0])
if (uploadResult.success) {
    console.log('Image URL:', uploadResult.data.url)
}

// Upload file
const fileResult = await appifio_client.appifio_uploadFile(fileInput.files[0])
```

### F. Auxiliary Operations (Advanced)

These methods are available for more specific file manipulations.

```javascript
// 1. Copy File
// Copies 'source.json' to 'backup.json'
const copyResult = await appifio_client.appifio_copyFile('source.json', 'backup.json')

// 2. Rename File
// Renames 'old_name.json' to 'new_name.json'
const renameResult = await appifio_client.appifio_renameFile('old_name.json', 'new_name.json')

// 3. Search Files
// Search for files containing "keyword" in name or content
const searchResult = await appifio_client.appifio_searchFiles('keyword')
// Returns array of matching files

// 4. Read Specific Field (JSON Optimization)
// Only reads the "metadata.total" field from "blog.json" instead of downloading the whole file
// Useful for large JSON files
const fieldResult = await appifio_client.appifio_readField('blog.json', 'metadata.total')

// 5. Write Specific Field (JSON Optimization)
// Updates only "metadata.last_updated" in "blog.json"
const updateFieldResult = await appifio_client.appifio_writeField('blog.json', 'metadata.last_updated', '2025-12-31')
```

---

## 🎯 DATA STRUCTURE PATTERN: Single Index File

### ⚠️ CRITICAL CONCEPT

**Use ONE file to store ALL entries of the same type.**

### Example: Blog System with Tags and Categories

**Structure:**
```
TempDB Storage:
├── blog.json              ← ALL blogs stored here

Filesystem:
├── routes.json            ← Route mappings (MANDATORY)
├── index.html            ← Main page
└── blog/
    ├── detail.html       ← Blog detail page handler
    ├── tag.html          ← Tag listing page handler
    └── category.html     ← Category listing page handler
```

**blog.json content:**
```json
{
  "blogs": {
    "1735302550000": {
      "id": "1735302550000",
      "title": "Bài viết về AI",
      "slug": "bai-viet-ve-ai",
      "author": "Admin",
      "content": "<p>Nội dung đầy đủ của bài viết...</p>",
      "excerpt": "Mô tả ngắn gọn...",
      "tags": ["AI", "Technology", "JavaScript"],
      "category": "Công nghệ",
      "created_at": "2025-12-27 15:59:10",
      "updated_at": "2025-12-27 15:59:10"
    }
  },
  "metadata": {
    "total": 1,
    "last_updated": "2025-12-27 15:59:10"
  }
}
```

**routes.json content (MANDATORY):**
```json
{
  "routes": {
    "bai-viet-ve-ai": {
      "file": "blog/detail.html",
      "type": "blog",
      "enabled": true
    },
    "tag/javascript": {
      "file": "blog/tag.html",
      "type": "tag",
      "enabled": true
    },
    "category/cong-nghe": {
      "file": "blog/category.html",
      "type": "category",
      "enabled": true
    }
  },
  "fallback": "index.html"
}
```

---

## 💻 COMPLETE IMPLEMENTATION EXAMPLES

### Example 1: Create Blog with Tags and Categories

```javascript
async function createBlog(title, author, content, tags = [], category = 'Uncategorized') {
    try {
        // STEP 1: Initialize with empty structure
        let blogIndex = {
            blogs: {},
            metadata: { total: 0, last_updated: '' }
        }
        
        // STEP 2: Try to read existing file
        try {
            if (appifio_client && appifio_client.appifio_readFile) {
                const result = await appifio_client.appifio_readFile('blog.json')
                if (result && result.success) {
                    blogIndex = JSON.parse(result.data.content)
                }
            }
        } catch (error) {
            console.log('Creating new blog.json')
        }
        
        // STEP 3: Generate ID and slug
        const blogId = Date.now().toString()
        const slug = appifio_client.appifio_generateSlug(title)
        
        // STEP 4: Create excerpt
        const tempDiv = document.createElement('div')
        tempDiv.innerHTML = content
        const textContent = tempDiv.textContent || tempDiv.innerText || ''
        const excerpt = textContent.substring(0, 150) + (textContent.length > 150 ? '...' : '')
        
        // STEP 5: Create blog entry
        const now = new Date().toISOString().slice(0, 19).replace('T', ' ')
        blogIndex.blogs[blogId] = {
            id: blogId,
            title: title,
            slug: slug,
            author: author,
            content: content,
            excerpt: excerpt,
            tags: tags,
            category: category,
            created_at: now,
            updated_at: now
        }
        
        // STEP 6: Update metadata
        blogIndex.metadata.total = Object.keys(blogIndex.blogs).length
        blogIndex.metadata.last_updated = now
        
        // STEP 7: Save to TempDB
        const saveResult = await appifio_client.appifio_writeFile(
            'blog.json',
            JSON.stringify(blogIndex, null, 2)
        )
        
        if (!saveResult.success) {
            throw new Error('Failed to save: ' + saveResult.message)
        }
        
        // STEP 8: MANDATORY - Add route for blog post
        const routeResult = await appifio_client.appifio_addRoute(
            slug,
            'blog/detail.html',
            'blog'
        )
        
        if (!routeResult.success) {
            console.warn('Failed to add blog route:', routeResult.message)
        }
        
        // STEP 9: MANDATORY - Ensure routes for tags
        await ensureTagRoutes(tags)
        
        // STEP 10: MANDATORY - Ensure route for category
        await ensureCategoryRoute(category)
        
        console.log('Blog created successfully!')
        return { success: true, blog: blogIndex.blogs[blogId] }
        
    } catch (error) {
        console.error('Error creating blog:', error)
        return { success: false, error: error.message }
    }
}

// Helper: Ensure tag routes exist
async function ensureTagRoutes(tags) {
    if (!tags || tags.length === 0) return
    
    if (!appifio_client || !appifio_client.appifio_addRoute) {
        console.warn('AppStorage client not available')
        return
    }
    
    for (const tag of tags) {
        if (!tag || tag.trim() === '') continue
        
        const tagSlug = appifio_client.appifio_generateSlug(tag)
        if (!tagSlug) continue
        
        const tagRoute = 'tag/' + tagSlug
        
        // Check if route already exists
        try {
            const routesResult = await appifio_client.appifio_getRoutes()
            if (routesResult.success && routesResult.data) {
                const routes = routesResult.data.routes || {}
                if (routes[tagRoute]) {
                    console.log('Tag route already exists:', tagRoute)
                    continue
                }
            }
            
            // Create route for tag
            const routeResult = await appifio_client.appifio_addRoute(
                tagRoute,
                'blog/tag.html',
                'tag'
            )
            
            if (routeResult.success) {
                console.log('Tag route created:', tagRoute)
            } else {
                console.warn('Failed to create tag route:', tagRoute, routeResult.message)
            }
        } catch (error) {
            console.error('Error creating tag route:', tag, error)
        }
    }
}

// Helper: Ensure category route exists
async function ensureCategoryRoute(category) {
    if (!category || category.trim() === '') return
    
    if (!appifio_client || !appifio_client.appifio_addRoute) {
        console.warn('AppStorage client not available')
        return
    }
    
    const categorySlug = appifio_client.appifio_generateSlug(category)
    if (!categorySlug) return
    
    const categoryRoute = 'category/' + categorySlug
    
    // Check if route already exists
    try {
        const routesResult = await appifio_client.appifio_getRoutes()
        if (routesResult.success && routesResult.data) {
            const routes = routesResult.data.routes || {}
            if (routes[categoryRoute]) {
                console.log('Category route already exists:', categoryRoute)
                return
            }
        }
        
        // Create route for category
        const routeResult = await appifio_client.appifio_addRoute(
            categoryRoute,
            'blog/category.html',
            'category'
        )
        
        if (routeResult.success) {
            console.log('Category route created:', categoryRoute)
        } else {
            console.warn('Failed to create category route:', categoryRoute, routeResult.message)
        }
    } catch (error) {
        console.error('Error creating category route:', category, error)
    }
}
```

### Example 2: Delete Blog (with Route Removal)

```javascript
async function deleteBlog(blogId) {
    if (!confirm('Bạn có chắc muốn xóa bài viết này?')) {
        return
    }
    
    try {
        // STEP 1: Read current data
        if (!appifio_client || !appifio_client.appifio_readFile) {
            throw new Error('AppStorage client not available')
        }
        
        const result = await appifio_client.appifio_readFile('blog.json')
        
        if (!result || !result.success) {
            throw new Error('Blog index not found')
        }
        
        const blogIndex = JSON.parse(result.data.content)
        
        // STEP 2: Check if blog exists
        if (!blogIndex.blogs[blogId]) {
            throw new Error('Blog not found')
        }
        
        // STEP 3: Get slug (for route removal)
        const slug = blogIndex.blogs[blogId].slug
        
        // STEP 4: Remove from index
        delete blogIndex.blogs[blogId]
        
        // STEP 5: Update metadata
        const now = new Date().toISOString().slice(0, 19).replace('T', ' ')
        blogIndex.metadata.total = Object.keys(blogIndex.blogs).length
        blogIndex.metadata.last_updated = now
        
        // STEP 6: Save to TempDB
        const saveResult = await appifio_client.appifio_writeFile(
            'blog.json',
            JSON.stringify(blogIndex, null, 2)
        )
        
        if (!saveResult.success) {
            throw new Error('Failed to save: ' + saveResult.message)
        }
        
        // STEP 7: MANDATORY - Remove route
        if (appifio_client.appifio_removeRoute) {
            const routeResult = await appifio_client.appifio_removeRoute(slug)
            
            if (!routeResult.success) {
                console.warn('Route removal failed:', routeResult.message)
            } else {
                console.log('Route removed:', slug)
            }
        }
        
        console.log('Blog deleted successfully')
        alert('Đã xóa bài viết thành công')
        
        return { success: true }
        
    } catch (error) {
        console.error('Error deleting blog:', error)
        alert('Lỗi: ' + error.message)
        return { success: false, error: error.message }
    }
}
```

### Example 3: Blog Detail Page (blog/detail.html)

```html
<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Blog Detail</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
        .blog-header { border-bottom: 2px solid #333; padding-bottom: 20px; margin-bottom: 20px; }
        .blog-meta { color: #666; font-size: 0.9em; margin-top: 10px; }
        .blog-content { margin-top: 30px; }
        .blog-tags { margin-top: 30px; }
        .tag { display: inline-block; background: #e0e0e0; padding: 5px 10px; margin-right: 5px; border-radius: 3px; }
        .loading { text-align: center; padding: 50px; }
        .error { color: red; text-align: center; padding: 50px; }
    </style>
</head>
<body>
    <div id="loading" class="loading">Đang tải...</div>
    <div id="error" class="error" style="display: none;">Không tìm thấy bài viết</div>
    
    <div id="blog-container" style="display: none;">
        <div class="blog-header">
            <h1 id="blog-title"></h1>
            <div class="blog-meta">
                <span id="blog-author"></span> • 
                <span id="blog-date"></span>
            </div>
        </div>
        
        <div class="blog-content" id="blog-content"></div>
        
        <div class="blog-tags" id="blog-tags"></div>
        
        <div style="margin-top: 50px;">
            <a id="back-link" href="#">← Quay lại trang chủ</a>
        </div>
    </div>

    <script>
    // Helper: Get URL name
    function getUrlName() {
        if (window.appifio && window.appifio.global && window.appifio.global.apps && window.appifio.global.apps.url_name) {
            return window.appifio.global.apps.url_name;
        }
        const pathParts = window.location.pathname.split('/').filter(p => p);
        return pathParts[0] || '';
    }
    
    // Helper: Update back link with absolute URL
    function updateBackLink() {
        const backLink = document.getElementById('back-link');
        if (backLink) {
            const urlName = getUrlName();
            let siteUrl = window.location.origin;
            if (window.appifio && window.appifio.global && window.appifio.global.apps && window.appifio.global.apps.site_url) {
                siteUrl = window.appifio.global.apps.site_url.replace(/\/$/, '');
            }
            // Use absolute path to avoid base tag issues
            backLink.href = siteUrl + '/' + urlName + '/';
        }
    }
    
    // Wait for AppStorage to be ready
    document.addEventListener('DOMContentLoaded', async function() {
        try {
            updateBackLink();
            
            // Wait for AppStorage
            if (typeof window.waitForAppStorage === 'function') {
                await window.waitForAppStorage();
            } else if (typeof window.onAppStorageReady === 'function') {
                await new Promise((resolve) => {
                    window.onAppStorageReady(() => resolve());
                });
            } else {
                let retries = 0;
                while ((!appifio_client || !appifio_client.appifio_readFile) && retries < 50) {
                    await new Promise(resolve => setTimeout(resolve, 100));
                    retries++;
                }
            }
            
            // Get slug from URL
            // URL format: /urlname/slug
            const pathParts = window.location.pathname.split('/').filter(p => p);
            let slug = '';
            
            // Skip urlname, get the next part
            if (pathParts.length >= 2) {
                const secondPart = pathParts[1];
                // If not 'tag' or 'category', it's a blog slug
                if (secondPart !== 'tag' && secondPart !== 'category') {
                    slug = secondPart;
                }
            }
            
            console.log('Loading blog with slug:', slug);
            
            if (!slug) {
                showError('Không tìm thấy bài viết');
                return;
            }
            
            // Load blog.json
            if (!appifio_client || !appifio_client.appifio_readFile) {
                showError('AppStorage không khả dụng');
                return;
            }
            
            const result = await appifio_client.appifio_readFile('blog.json');
            
            if (!result || !result.success) {
                showError('Không tìm thấy dữ liệu blog');
                return;
            }
            
            const blogIndex = JSON.parse(result.data.content);
            
            // Find blog by slug
            const blog = Object.values(blogIndex.blogs).find(b => b.slug === slug);
            
            if (!blog) {
                showError('Không tìm thấy bài viết');
                return;
            }
            
            // Display blog
            displayBlog(blog);
            
        } catch (error) {
            console.error('Error loading blog:', error);
            showError('Lỗi: ' + error.message);
        }
    });
    
    function displayBlog(blog) {
        document.getElementById('loading').style.display = 'none';
        document.getElementById('blog-container').style.display = 'block';
        
        document.getElementById('blog-title').textContent = blog.title;
        document.getElementById('blog-author').textContent = 'Tác giả: ' + blog.author;
        
        // Format date
        let dateStr = blog.created_at;
        if (appifio_client && appifio_client.appifio_formatDateTime) {
            dateStr = appifio_client.appifio_formatDateTime(blog.created_at, 'date');
        }
        document.getElementById('blog-date').textContent = dateStr;
        
        // Content
        document.getElementById('blog-content').innerHTML = blog.content;
        
        // Tags with links
        if (blog.tags && blog.tags.length > 0) {
            const urlName = getUrlName();
            let siteUrl = window.location.origin;
            if (window.appifio && window.appifio.global && window.appifio.global.apps && window.appifio.global.apps.site_url) {
                siteUrl = window.appifio.global.apps.site_url.replace(/\/$/, '');
            }
            
            const tagsHtml = blog.tags.map(tag => {
                const tagSlug = appifio_client.appifio_generateSlug(tag);
                const tagUrl = siteUrl + '/' + urlName + '/tag/' + tagSlug;
                return `<a href="${tagUrl}" class="tag">${escapeHtml(tag)}</a>`;
            }).join('');
            document.getElementById('blog-tags').innerHTML = 'Tags: ' + tagsHtml;
        }
    }
    
    function showError(message) {
        document.getElementById('loading').style.display = 'none';
        const errorDiv = document.getElementById('error');
        errorDiv.textContent = message;
        errorDiv.style.display = 'block';
    }
    
    function escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }
    </script>
</body>
</html>
```

### Example 4: Tag Listing Page (blog/tag.html)

```html
<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tag: JavaScript</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
        .blog-item { border-bottom: 1px solid #eee; padding: 20px 0; }
        .blog-item h3 { margin: 0 0 10px 0; }
        .blog-item a { text-decoration: none; color: #333; }
        .blog-meta { color: #666; font-size: 0.9em; }
        .loading { text-align: center; padding: 50px; }
        .error { color: red; text-align: center; padding: 50px; }
    </style>
</head>
<body>
    <div id="loading" class="loading">Đang tải...</div>
    <div id="error" class="error" style="display: none;"></div>
    
    <div id="content" style="display: none;">
        <h1 id="tag-title">Bài viết với thẻ</h1>
        <div id="blog-list"></div>
        <div style="margin-top: 30px;">
            <a id="back-link" href="#">← Quay lại trang chủ</a>
        </div>
    </div>

    <script>
    // Helper functions
    function getUrlName() {
        if (window.appifio && window.appifio.global && window.appifio.global.apps && window.appifio.global.apps.url_name) {
            return window.appifio.global.apps.url_name;
        }
        const pathParts = window.location.pathname.split('/').filter(p => p);
        return pathParts[0] || '';
    }
    
    function updateBackLink() {
        const backLink = document.getElementById('back-link');
        if (backLink) {
            const urlName = getUrlName();
            let siteUrl = window.location.origin;
            if (window.appifio && window.appifio.global && window.appifio.global.apps && window.appifio.global.apps.site_url) {
                siteUrl = window.appifio.global.apps.site_url.replace(/\/$/, '');
            }
            backLink.href = siteUrl + '/' + urlName + '/';
        }
    }
    
    function generateSlug(text) {
        if (!text) return '';
        if (appifio_client && appifio_client.appifio_generateSlug) {
            return appifio_client.appifio_generateSlug(text);
        }
        return text.toLowerCase()
            .normalize('NFD')
            .replace(/[\u0300-\u036f]/g, '')
            .replace(/[^\w\s-]/g, '')
            .replace(/\s+/g, '-')
            .replace(/-+/g, '-')
            .replace(/^-+|-+$/g, '');
    }
    
    function escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }
    
    // Main script
    document.addEventListener('DOMContentLoaded', async function() {
        try {
            updateBackLink();
            
            // Get tag slug from URL
            // URL format: /urlname/tag/tag-slug
            const pathParts = window.location.pathname.split('/').filter(p => p);
            let tagSlug = '';
            
            const tagIndex = pathParts.indexOf('tag');
            if (tagIndex !== -1 && pathParts.length > tagIndex + 1) {
                tagSlug = pathParts[tagIndex + 1];
            }
            
            console.log('Loading blogs with tag slug:', tagSlug);
            
            if (!tagSlug) {
                showError('Không tìm thấy thẻ');
                return;
            }
            
            // Wait for AppStorage
            if (typeof window.waitForAppStorage === 'function') {
                await window.waitForAppStorage();
            } else if (typeof window.onAppStorageReady === 'function') {
                await new Promise((resolve) => {
                    window.onAppStorageReady(() => resolve());
                });
            } else {
                let retries = 0;
                while ((!appifio_client || !appifio_client.appifio_readFile) && retries < 50) {
                    await new Promise(resolve => setTimeout(resolve, 100));
                    retries++;
                }
            }
            
            await loadTagBlogs(tagSlug);
            
        } catch (error) {
            console.error('Error:', error);
            showError('Lỗi khi tải bài viết');
        }
    });
    
    async function loadTagBlogs(tagSlug) {
        try {
            let blogIndex = { blogs: {}, metadata: { total: 0 } };
            
            if (appifio_client && appifio_client.appifio_readFile) {
                const result = await appifio_client.appifio_readFile('blog.json');
                
                if (result && result.success) {
                    blogIndex = JSON.parse(result.data.content);
                } else {
                    showError('Không tìm thấy dữ liệu blog');
                    return;
                }
            } else {
                showError('AppStorage không khả dụng');
                return;
            }
            
            // Filter blogs by tag
            const tagBlogs = Object.values(blogIndex.blogs).filter(blog => {
                if (!blog.tags || !Array.isArray(blog.tags)) return false;
                return blog.tags.some(tag => {
                    const tagSlugNormalized = generateSlug(tag);
                    return tagSlugNormalized === tagSlug;
                });
            });
            
            if (tagBlogs.length === 0) {
                showError('Không tìm thấy bài viết nào với thẻ này');
                return;
            }
            
            // Get tag name from first blog
            const firstBlog = tagBlogs[0];
            const tagName = firstBlog.tags.find(tag => {
                const tagSlugNormalized = generateSlug(tag);
                return tagSlugNormalized === tagSlug;
            });
            
            // Display blogs
            displayTagBlogs(tagName, tagBlogs);
            
        } catch (error) {
            console.error('Error loading tag blogs:', error);
            showError('Lỗi: ' + error.message);
        }
    }
    
    function displayTagBlogs(tagName, blogs) {
        document.getElementById('loading').style.display = 'none';
        document.getElementById('content').style.display = 'block';
        
        document.getElementById('tag-title').textContent = 'Bài viết với thẻ: ' + tagName;
        document.title = 'Thẻ: ' + tagName + ' - Blog';
        
        const blogList = document.getElementById('blog-list');
        const urlName = getUrlName();
        let siteUrl = window.location.origin;
        if (window.appifio && window.appifio.global && window.appifio.global.apps && window.appifio.global.apps.site_url) {
            siteUrl = window.appifio.global.apps.site_url.replace(/\/$/, '');
        }
        
        blogs.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
        
        let html = '';
        blogs.forEach(blog => {
            html += `
                <div class="blog-item">
                    <h3><a href="${siteUrl}/${urlName}/${blog.slug}">${escapeHtml(blog.title)}</a></h3>
                    <div class="blog-meta">
                        Tác giả: ${escapeHtml(blog.author)} • 
                        ${formatDate(blog.created_at)}
                    </div>
                    <div>${escapeHtml(blog.excerpt || '')}</div>
                </div>
            `;
        });
        
        blogList.innerHTML = html;
    }
    
    function formatDate(dateString) {
        if (appifio_client && appifio_client.appifio_formatDateTime) {
            return appifio_client.appifio_formatDateTime(dateString, 'date');
        }
        const date = new Date(dateString);
        return date.toLocaleDateString('vi-VN', {
            day: '2-digit',
            month: '2-digit',
            year: 'numeric'
        });
    }
    
    function showError(message) {
        document.getElementById('loading').style.display = 'none';
        const errorDiv = document.getElementById('error');
        errorDiv.textContent = message;
        errorDiv.style.display = 'block';
    }
    </script>
</body>
</html>
```

### Example 5: Category Listing Page (blog/category.html)

Similar to tag.html, but filters by category instead of tags:

```javascript
// In blog/category.html, extract category slug from URL:
// URL format: /urlname/category/category-slug
const pathParts = window.location.pathname.split('/').filter(p => p);
let categorySlug = '';

const categoryIndex = pathParts.indexOf('category');
if (categoryIndex !== -1 && pathParts.length > categoryIndex + 1) {
    categorySlug = pathParts[categoryIndex + 1];
}

// Filter blogs by category:
const categoryBlogs = Object.values(blogIndex.blogs).filter(blog => {
    if (!blog.category) return false;
    const blogCategorySlug = generateSlug(blog.category);
    return blogCategorySlug === categorySlug;
});
```

---

## 📋 CRITICAL RULES

### ✅ ALWAYS DO:

1. **Initialize with default structure** before reading files
2. **Handle file not found gracefully** - it's normal for first-time
3. **Single Index File:** Store all blogs in ONE `blog.json` file
4. **Use ID as Key:** Structure as `{ blogs: { "id": {...} } }`
5. **Full Content:** Store complete blog content in `content` field
6. **Generate Slug:** Use `appifio_generateSlug()` for URL-friendly slugs
7. **Update Metadata:** Always update `metadata.total` and `metadata.last_updated`
8. **MANDATORY Routes:** Create route entry for EVERY blog, tag, and category
9. **Nested Routes:** Use `tag/slug` and `category/slug` format for nested routes
10. **Create Handler Files:** Blog detail/tag/category HTML must exist in filesystem
11. **Absolute URLs:** Always use absolute URLs for navigation (base tag issue)
12. **Remove Routes:** Always remove routes when deleting content
13. **Error Handling:** Use try-catch for all async operations
14. **Check Client:** Always check `appifio_client && appifio_client.methodName` before calling

### ❌ NEVER DO:

1. ❌ Assume file exists without checking
2. ❌ Create separate files per blog (`blog-slug.json`)
3. ❌ Store only metadata without full content
4. ❌ Hardcode IDs
5. ❌ Forget to update metadata section
6. ❌ Skip route creation (URLs won't work!)
7. ❌ Forget to create handler HTML files
8. ❌ Use relative URLs for navigation (base tag breaks them)
9. ❌ Forget to remove routes when deleting
10. ❌ Check `typeof appifio_client === 'undefined'` (it's a getter, always exists)
11. ❌ Define `class AppStorageClient`
12. ❌ Rewrite `appifio_*` methods

---

## 🔍 STORAGE SEPARATION

### TempDB (app_storage table)
**Purpose:** Store data files  
**Use for:** `blog.json`, `settings.json`, `user-data.json`  
**Methods:** `appifio_readFile()`, `appifio_writeFile()`

### Filesystem (sync_data/urlname/)
**Purpose:** Store HTML files and configuration  
**Use for:** `routes.json`, `blog/detail.html`, `index.html`, `blog/tag.html`, `blog/category.html`  
**Methods:** `appifio_readFsFile()`, `appifio_writeFsFile()`

---

## 🎯 COMPLETE WORKFLOW

### First-Time Setup:
```
1. Create blog/detail.html, blog/tag.html, blog/category.html in filesystem
   ↓
2. User creates first blog
   ↓
3. blog.json is created (writeFile)
   ↓
4. Route is added to routes.json for blog post
   ↓
5. Routes are added for tags and category
   ↓
6. System can now serve /urlname/blog-slug, /urlname/tag/tag-slug, /urlname/category/category-slug
```

### Creating a Blog:
```
1. Read blog.json (or use empty structure if not found)
2. Generate ID (timestamp)
3. Generate slug from title
4. Add new entry to blogs object
5. Update metadata
6. Save blog.json to TempDB
7. Add route to routes.json for blog post (MANDATORY)
8. Ensure routes exist for all tags (tag/tag-slug format)
9. Ensure route exists for category (category/category-slug format)
10. User can now visit /urlname/slug, /urlname/tag/tag-slug, /urlname/category/category-slug
```

### Displaying a Blog:
```
User visits: /urlname/my-blog-slug
    ↓
System reads: routes.json
    ↓
Finds: "my-blog-slug" → "blog/detail.html"
    ↓
Serves: blog/detail.html
    ↓
blog/detail.html reads: blog.json (TempDB)
    ↓
Finds blog with slug: "my-blog-slug"
    ↓
Displays: blog content
```

### Displaying Tag/Category:
```
User visits: /urlname/tag/javascript
    ↓
System reads: routes.json
    ↓
Finds: "tag/javascript" → "blog/tag.html"
    ↓
Serves: blog/tag.html
    ↓
blog/tag.html extracts slug "javascript" from URL
    ↓
Reads: blog.json (TempDB)
    ↓
Filters blogs by tag slug
    ↓
Displays: filtered blog list
```

---

## 💡 DEBUGGING TIPS

### If blog.json not found:
```javascript
// ✅ This is NORMAL for first time
console.log('blog.json not found - will create on first save')
```

### If route doesn't work:
```javascript
// Check if route exists
const routes = await appifio_client.appifio_getRoutes()
console.log('All routes:', routes.data.routes)

// Check specific route
if (!routes.data.routes['my-slug']) {
    console.error('Route missing! Add it:')
    await appifio_client.appifio_addRoute('my-slug', 'blog/detail.html', 'blog')
}
```

### If nested route doesn't work:
```javascript
// Check nested route
const routes = await appifio_client.appifio_getRoutes()
if (!routes.data.routes['tag/javascript']) {
    console.error('Nested route missing! Add it:')
    await appifio_client.appifio_addRoute('tag/javascript', 'blog/tag.html', 'tag')
}
```

### If handler file doesn't exist:
```javascript
// Check if file exists
const result = await appifio_client.appifio_readFsFile('blog/detail.html')
if (!result.success) {
    console.error('Handler file missing! Create it:')
    await appifio_client.appifio_writeFsFile('blog/detail.html', htmlContent)
}
```

### If navigation links broken:
```javascript
// Always use absolute URLs
const urlName = getUrlName();
let siteUrl = window.location.origin;
if (window.appifio && window.appifio.global && window.appifio.global.apps && window.appifio.global.apps.site_url) {
    siteUrl = window.appifio.global.apps.site_url.replace(/\/$/, '');
}
const absoluteUrl = siteUrl + '/' + urlName + '/path';
```

---

## 🎓 FINAL REMINDER

You are building an APPLICATION that USES a pre-built API.

The API library is already written and injected.  
Your job: Write business logic using the API.  
Focus: User interface, data flow, user experience.  
Don't waste time: Reimplementing what already exists.

### Key Points:

- File not found is NORMAL for first-time usage
- Routes are MANDATORY for all non-index URLs
- Nested routes work: `tag/slug`, `category/slug`
- Handler HTML files MUST exist in filesystem
- Always initialize with default structure
- Always add routes when creating content
- Always remove routes when deleting content
- Always use absolute URLs for navigation
- Always check `appifio_client && appifio_client.methodName` before calling

Build great applications with the tools provided!

```

