monorepo/web/maplepress-frontend/docs/API/SITE_API.md

27 KiB

Site API Implementation (WordPress Site Management)

This document describes the implementation of the WordPress Site Management API endpoints for the MaplePress frontend, integrated with the MaplePress backend API.

Overview

The Site API endpoints manage WordPress sites and their API credentials for the MaplePress plugin. Sites are automatically scoped to tenants (determined from JWT token) and include API key management, usage tracking, and search index configuration.

Backend API Endpoints

Create WordPress Site

Endpoint: POST /api/v1/sites Authentication: Required (JWT token)

List WordPress Sites

Endpoint: GET /api/v1/sites Authentication: Required (JWT token)

Get WordPress Site

Endpoint: GET /api/v1/sites/{id} Authentication: Required (JWT token)

Delete WordPress Site

Endpoint: DELETE /api/v1/sites/{id} Authentication: Required (JWT token)

Rotate Site API Key

Endpoint: POST /api/v1/sites/{id}/rotate-api-key Authentication: Required (JWT token)

Documentation: /cloud/maplepress-backend/docs/API.md (lines 805-1085)

Request/Response Structures

Create Site Request

{
  "site_url": "https://example.com"
}

Note: The backend automatically extracts the domain from the site_url.

Headers Required:

  • Content-Type: application/json
  • Authorization: JWT {access_token}

Create Site Response

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "domain": "example.com",
  "site_url": "https://example.com",
  "api_key": "live_sk_1234567890abcdef...",
  "verification_token": "verify_abc123...",
  "status": "pending",
  "search_index_name": "site_550e8400-e29b-41d4-a716-446655440000"
}

⚠️ IMPORTANT: The api_key is shown ONLY ONCE on site creation. Store it immediately!

List Sites Request

GET /api/v1/sites?page_size=20&page_state=encoded_token

Query Parameters:

  • page_size (optional): Number of results per page (default: 20, max: 100)
  • page_state (optional): Pagination token from previous response

List Sites Response

{
  "sites": [
    {
      "id": "550e8400-...",
      "domain": "example.com",
      "status": "active",
      "is_verified": true,
      "created_at": "2024-10-24T00:00:00Z"
    }
  ],
  "page_state": "encoded_pagination_token"
}

Get Site Response

{
  "id": "550e8400-...",
  "tenant_id": "850e8400-...",
  "domain": "example.com",
  "site_url": "https://example.com",
  "api_key_prefix": "live_sk_",
  "api_key_last_four": "f123",
  "status": "active",
  "is_verified": true,
  "search_index_name": "site_550e8400-...",
  "total_pages_indexed": 150,
  "last_indexed_at": "2024-10-24T12:00:00Z",
  "plugin_version": "1.0.0",
  "storage_used_bytes": 52428800,
  "search_requests_count": 1250,
  "monthly_pages_indexed": 45,
  "last_reset_at": "2024-10-01T00:00:00Z",
  "created_at": "2024-10-01T00:00:00Z",
  "updated_at": "2024-10-24T12:00:00Z"
}

Rotate API Key Response

{
  "new_api_key": "live_sk_9876543210fedcba...",
  "old_key_last_four": "f123",
  "rotated_at": "2024-10-24T12:00:00Z"
}

⚠️ CRITICAL:

  • The new_api_key is shown ONLY ONCE. Store it immediately!
  • The old API key is immediately invalidated - no grace period!
  • Your WordPress site will stop working until you update the plugin with the new key

Frontend Implementation

SiteService (src/services/API/SiteService.js)

Handles all WordPress Site Management operations.

Key Features:

  • Create new WordPress sites with API key generation
  • List all sites with pagination support
  • Get detailed site information including usage statistics
  • Hard delete sites (irreversible)
  • Rotate API keys with grace period
  • Client-side validation (domain and URL)
  • Response transformation (snake_case to camelCase)
  • User-friendly error message mapping

Methods:

createSite(siteData)

Create a new WordPress site and generate API credentials.

import SiteService from './services/API/SiteService';

const site = await SiteService.createSite({
  siteUrl: "https://example.com"
});

console.log(site);
// Output:
// {
//   id: "550e8400-...",
//   domain: "example.com",  // Extracted from siteUrl by backend
//   siteUrl: "https://example.com",
//   apiKey: "live_sk_...",  // SAVE THIS NOW!
//   verificationToken: "verify_...",
//   status: "pending",
//   searchIndexName: "site_550e8400-..."
// }

Parameters:

Note: The backend automatically extracts the domain (e.g., "example.com") from the siteUrl.

Returns:

{
  id: string,                  // Site ID (UUID)
  domain: string,              // Site domain
  siteUrl: string,             // Full site URL
  apiKey: string,              // API key (SHOWN ONLY ONCE!)
  verificationToken: string,   // Token for plugin verification
  status: string,              // Site status ("pending" or "active")
  searchIndexName: string      // Meilisearch index name
}

Throws:

  • "Site data is required" - If siteData is missing
  • "Site URL is required" - If siteUrl is missing
  • "Invalid site URL format" - If URL format is invalid
  • "Could not extract domain from URL" - If domain cannot be extracted
  • "This domain is already registered. Each domain can only be registered once." - Domain conflict (409)
  • "Authentication required. Please log in to continue." - Missing/invalid token

listSites(options)

List all WordPress sites for the authenticated user's tenant with pagination.

// First page
const result = await SiteService.listSites({ pageSize: 20 });
console.log(result.sites);      // Array of sites
console.log(result.pageState);  // Token for next page

// Next page
if (result.pageState) {
  const nextPage = await SiteService.listSites({
    pageSize: 20,
    pageState: result.pageState
  });
}

Parameters:

  • options.pageSize (number, optional): Number of results per page (default: 20, max: 100)
  • options.pageState (string, optional): Pagination token from previous response

Returns:

{
  sites: Array<{
    id: string,
    domain: string,
    status: string,
    isVerified: boolean,
    createdAt: Date
  }>,
  pageState: string|null  // Pagination token for next page
}

Throws:

  • "Page size must be between 1 and 100" - Invalid page size
  • "Authentication required. Please log in to continue." - Missing/invalid token

getSiteById(siteId)

Get detailed information about a specific WordPress site including usage statistics.

const site = await SiteService.getSiteById("550e8400-...");
console.log(site.domain);              // "example.com"
console.log(site.totalPagesIndexed);   // 150
console.log(site.storageUsedBytes);    // 52428800

// Format storage for display
const storage = SiteService.formatStorage(site.storageUsedBytes);
console.log(storage);  // "50 MB"

Parameters:

  • siteId (string, required): Site ID (UUID format)

Returns:

{
  id: string,
  tenantId: string,
  domain: string,
  siteUrl: string,
  apiKeyPrefix: string,
  apiKeyLastFour: string,
  status: string,
  isVerified: boolean,
  searchIndexName: string,
  totalPagesIndexed: number,
  lastIndexedAt: Date|null,
  pluginVersion: string,
  storageUsedBytes: number,
  searchRequestsCount: number,
  monthlyPagesIndexed: number,
  lastResetAt: Date|null,
  createdAt: Date,
  updatedAt: Date
}

Throws:

  • "Site ID is required" - If ID is missing
  • "Invalid site ID format" - If ID is not a valid UUID
  • "Site not found or doesn't belong to your organization." - If site doesn't exist (404)
  • "Authentication required. Please log in to continue." - Missing/invalid token

deleteSite(siteId)

Delete a WordPress site and all associated data (irreversible).

const result = await SiteService.deleteSite("550e8400-...");
console.log(result.success);  // true
console.log(result.message);  // "Site deleted successfully"

⚠️ WARNING: This is a hard delete operation. All site data, including:

  • API keys (immediately invalidated)
  • Search index data
  • Usage statistics
  • Configuration

...will be permanently deleted and cannot be recovered.

Parameters:

  • siteId (string, required): Site ID (UUID format)

Returns:

{
  success: boolean,
  message: string
}

Throws:

  • "Site ID is required" - If ID is missing
  • "Invalid site ID format" - If ID is not a valid UUID
  • "Site not found or doesn't belong to your organization." - If site doesn't exist (404)
  • "Authentication required. Please log in to continue." - Missing/invalid token

rotateApiKey(siteId)

Rotate a site's API key (use when the key is compromised).

const result = await SiteService.rotateApiKey("550e8400-...");
console.log(result.newApiKey);         // "live_sk_..." - SAVE THIS NOW!
console.log(result.oldKeyLastFour);    // "f123"
console.log(result.rotatedAt);         // Date (now)

🚨 CRITICAL:

  • The newApiKey is shown ONLY ONCE. Store it immediately!
  • The old API key is immediately invalidated - no grace period!
  • Your WordPress site will stop working until you update the plugin with the new key
  • Update your WordPress plugin settings RIGHT NOW to restore functionality

Parameters:

  • siteId (string, required): Site ID (UUID format)

Returns:

{
  newApiKey: string,           // New API key (shown only once!)
  oldKeyLastFour: string,      // Last 4 chars of old key
  rotatedAt: Date              // Rotation timestamp
}

Throws:

  • "Site ID is required" - If ID is missing
  • "Invalid site ID format" - If ID is not a valid UUID
  • "Site not found or doesn't belong to your organization." - If site doesn't exist (404)
  • "Authentication required. Please log in to continue." - Missing/invalid token

validateDomain(domain)

Validate domain format.

const result = SiteService.validateDomain("example.com");
console.log(result); // { valid: true, error: null }

const invalid = SiteService.validateDomain("invalid..domain");
console.log(invalid); // { valid: false, error: "Invalid domain format" }

Returns: { valid: boolean, error: string|null }

Validation Rules:

  • Required (non-empty)
  • Valid domain format (e.g., example.com, subdomain.example.com)
  • No protocol (http/https should be in siteUrl)

validateSiteUrl(url)

Validate site URL format.

const result = SiteService.validateSiteUrl("https://example.com");
console.log(result); // { valid: true, error: null }

const invalid = SiteService.validateSiteUrl("ftp://example.com");
console.log(invalid); // { valid: false, error: "Site URL must use http or https protocol" }

Returns: { valid: boolean, error: string|null }

Validation Rules:

  • Required (non-empty)
  • Valid URL format
  • Must use http:// or https:// protocol

formatStorage(bytes)

Format storage bytes to human-readable format.

const formatted = SiteService.formatStorage(52428800);
console.log(formatted); // "50 MB"

const kb = SiteService.formatStorage(2048);
console.log(kb); // "2 KB"

Returns: string (e.g., "50 MB", "2 KB", "1.5 GB")

Data Flow

Create Site Flow

User provides site URL
    ↓
SiteService.createSite()
    ↓
Validate URL (client-side)
    ↓
ApiClient.post() with JWT token
    ↓
Token automatically refreshed if needed
    ↓
POST /api/v1/sites with tenant from JWT
    ↓
Backend extracts domain from site URL
    ↓
Backend validates domain format
    ↓
Backend generates API key and verification token
    ↓
Backend creates Meilisearch index
    ↓
Backend returns site data with API key and extracted domain
    ↓
SiteService transforms response
    ↓
Component displays API key (ONLY TIME IT'S SHOWN!)

List Sites Flow

Component needs site list
    ↓
SiteService.listSites()
    ↓
ApiClient.get() with JWT token and pagination params
    ↓
Token automatically refreshed if needed
    ↓
GET /api/v1/sites with tenant from JWT
    ↓
Backend retrieves sites for tenant
    ↓
Backend returns paginated results
    ↓
SiteService transforms response
    ↓
Component receives sites array and next page token

Rotate API Key Flow

User requests key rotation
    ↓
SiteService.rotateApiKey()
    ↓
ApiClient.post() with JWT token
    ↓
Token automatically refreshed if needed
    ↓
POST /api/v1/sites/{id}/rotate-api-key
    ↓
Backend generates new API key
    ↓
Backend IMMEDIATELY invalidates old key (DELETE from DB)
    ↓
Backend inserts new key into database
    ↓
Backend returns new key (old key NO LONGER WORKS!)
    ↓
SiteService transforms response
    ↓
Component displays new key (ONLY TIME IT'S SHOWN!)
    ↓
User MUST update WordPress plugin NOW to restore functionality

Error Handling

Error Types

Error Condition Response Frontend Behavior
Missing authentication 401 Unauthorized "Authentication required."
Invalid site data 400 Bad Request Specific validation error
Domain already exists 409 Conflict "Domain already registered by another user."
Site not found 404 Not Found "Site not found or doesn't belong to your organization."
Server error 500 Internal Server Error Generic error message

Usage Examples

Create a New Site

import React, { useState } from 'react';
import SiteService from '../../services/API/SiteService';

function CreateSiteForm() {
  const [siteUrl, setSiteUrl] = useState('');
  const [apiKey, setApiKey] = useState('');
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setError('');
    setLoading(true);

    // Validate URL before sending
    const urlValidation = SiteService.validateSiteUrl(siteUrl);
    if (!urlValidation.valid) {
      setError(urlValidation.error);
      setLoading(false);
      return;
    }

    try {
      const site = await SiteService.createSite({ siteUrl });

      // CRITICAL: Display API key immediately - it's shown only once!
      setApiKey(site.apiKey);
      alert(`IMPORTANT: Save this API key now! ${site.apiKey}`);

      console.log("Site created:", site);
      console.log("Domain extracted by backend:", site.domain);
      // Reset form
      setSiteUrl('');
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>WordPress Site URL:</label>
        <input
          type="url"
          value={siteUrl}
          onChange={(e) => setSiteUrl(e.target.value)}
          placeholder="https://example.com"
        />
        <p className="help-text">
          The domain will be automatically extracted from this URL
        </p>
      </div>

      {error && <p className="error">{error}</p>}

      {apiKey && (
        <div className="api-key-display">
          <h3>⚠️ API Key (Save Now!)</h3>
          <code>{apiKey}</code>
          <p>This key will not be shown again!</p>
        </div>
      )}

      <button type="submit" disabled={loading}>
        {loading ? 'Creating...' : 'Create Site'}
      </button>
    </form>
  );
}

export default CreateSiteForm;

List Sites with Pagination

import React, { useEffect, useState } from 'react';
import SiteService from '../../services/API/SiteService';

function SiteList() {
  const [sites, setSites] = useState([]);
  const [pageState, setPageState] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');

  const loadSites = async (nextPageState = null) => {
    try {
      setLoading(true);
      const result = await SiteService.listSites({
        pageSize: 20,
        pageState: nextPageState
      });

      if (nextPageState) {
        // Append to existing sites
        setSites(prev => [...prev, ...result.sites]);
      } else {
        // First page
        setSites(result.sites);
      }

      setPageState(result.pageState);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    loadSites();
  }, []);

  return (
    <div className="site-list">
      <h2>WordPress Sites</h2>

      {error && <p className="error">{error}</p>}

      {sites.map(site => (
        <div key={site.id} className="site-item">
          <h3>{site.domain}</h3>
          <p>Status: {site.status}</p>
          <p>Verified: {site.isVerified ? 'Yes' : 'No'}</p>
          <p>Created: {site.createdAt.toLocaleDateString()}</p>
        </div>
      ))}

      {loading && <p>Loading sites...</p>}

      {pageState && !loading && (
        <button onClick={() => loadSites(pageState)}>
          Load More
        </button>
      )}
    </div>
  );
}

export default SiteList;

Display Site Details with Usage

import React, { useEffect, useState } from 'react';
import SiteService from '../../services/API/SiteService';

function SiteDetails({ siteId }) {
  const [site, setSite] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');

  useEffect(() => {
    const fetchSite = async () => {
      try {
        const data = await SiteService.getSiteById(siteId);
        setSite(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchSite();
  }, [siteId]);

  if (loading) return <div>Loading site...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!site) return null;

  return (
    <div className="site-details">
      <h2>{site.domain}</h2>
      <p>URL: <a href={site.siteUrl}>{site.siteUrl}</a></p>
      <p>Status: {site.status}</p>
      <p>Verified: {site.isVerified ? 'Yes' : 'No'}</p>

      <h3>API Key</h3>
      <p>Prefix: {site.apiKeyPrefix}</p>
      <p>Last 4 digits: {site.apiKeyLastFour}</p>

      <h3>Usage Statistics</h3>
      <p>Total Pages Indexed: {site.totalPagesIndexed}</p>
      <p>Monthly Pages Indexed: {site.monthlyPagesIndexed}</p>
      <p>Storage Used: {SiteService.formatStorage(site.storageUsedBytes)}</p>
      <p>Search Requests: {site.searchRequestsCount}</p>

      {site.lastIndexedAt && (
        <p>Last Indexed: {site.lastIndexedAt.toLocaleString()}</p>
      )}

      <h3>Plugin</h3>
      <p>Version: {site.pluginVersion || 'Not connected'}</p>

      <h3>Search Index</h3>
      <p>Index Name: {site.searchIndexName}</p>
    </div>
  );
}

export default SiteDetails;

Rotate API Key

import React, { useState } from 'react';
import SiteService from '../../services/API/SiteService';

function RotateApiKey({ siteId, onRotated }) {
  const [newKey, setNewKey] = useState('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');

  const handleRotate = async () => {
    if (!confirm('🚨 CRITICAL WARNING: Are you sure you want to rotate the API key?\n\nThe old key will be IMMEDIATELY INVALIDATED (no grace period).\nYour WordPress site will stop working until you update the plugin!')) {
      return;
    }

    setLoading(true);
    setError('');

    try {
      const result = await SiteService.rotateApiKey(siteId);

      // CRITICAL: Display new API key immediately - it's shown only once!
      setNewKey(result.newApiKey);
      alert(`🚨 CRITICAL: Save this new API key NOW!\n\n${result.newApiKey}\n\nOld key (ending in ${result.oldKeyLastFour}) has been IMMEDIATELY INVALIDATED.\n\nUpdate your WordPress plugin RIGHT NOW to restore functionality!`);

      if (onRotated) {
        onRotated(result);
      }
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="rotate-api-key">
      <h3>Rotate API Key</h3>
      <p>Use this if your API key has been compromised.</p>

      {error && <p className="error">{error}</p>}

      {newKey && (
        <div className="api-key-display">
          <h4>🚨 New API Key (Save Now!)</h4>
          <code>{newKey}</code>
          <p>This key will not be shown again!</p>
          <p className="critical">Old key is IMMEDIATELY INVALIDATED - Update your WordPress plugin NOW!</p>
        </div>
      )}

      <button onClick={handleRotate} disabled={loading}>
        {loading ? 'Rotating...' : 'Rotate API Key'}
      </button>
    </div>
  );
}

export default RotateApiKey;

Delete Site

import React, { useState } from 'react';
import SiteService from '../../services/API/SiteService';

function DeleteSite({ siteId, siteDomain, onDeleted }) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');

  const handleDelete = async () => {
    const confirmed = confirm(
      `⚠️ WARNING: This will PERMANENTLY delete "${siteDomain}" and ALL associated data:\n\n` +
      `- API keys (immediately invalidated)\n` +
      `- Search index data\n` +
      `- Usage statistics\n` +
      `- Configuration\n\n` +
      `This action CANNOT be undone!\n\n` +
      `Are you absolutely sure?`
    );

    if (!confirmed) return;

    // Double confirmation
    const doubleConfirm = prompt(`Type "${siteDomain}" to confirm deletion:`);
    if (doubleConfirm !== siteDomain) {
      alert('Domain name did not match. Deletion cancelled.');
      return;
    }

    setLoading(true);
    setError('');

    try {
      const result = await SiteService.deleteSite(siteId);
      console.log(result.message);

      if (onDeleted) {
        onDeleted(siteId);
      }
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="delete-site">
      <h3>Danger Zone</h3>
      {error && <p className="error">{error}</p>}
      <button
        onClick={handleDelete}
        disabled={loading}
        className="btn-danger"
      >
        {loading ? 'Deleting...' : 'Delete Site'}
      </button>
      <p className="warning">This action cannot be undone!</p>
    </div>
  );
}

export default DeleteSite;

Testing

Test Create Site

// In browser console after login

import SiteService from './services/API/SiteService';

// Validate URL
const urlValidation = SiteService.validateSiteUrl("https://example.com");
console.log("URL valid:", urlValidation.valid);

// Create site (backend extracts domain automatically)
const site = await SiteService.createSite({
  siteUrl: "https://example.com"
});

console.log("Created site:", site);
console.log("Domain extracted:", site.domain);  // "example.com"
console.log("⚠️ SAVE THIS API KEY:", site.apiKey);

Test List Sites

// List first page
const result = await SiteService.listSites({ pageSize: 10 });
console.log("Sites:", result.sites);
console.log("Next page token:", result.pageState);

// Load next page
if (result.pageState) {
  const nextPage = await SiteService.listSites({
    pageSize: 10,
    pageState: result.pageState
  });
  console.log("Next page:", nextPage.sites);
}

Test Get Site

// Using site ID from creation
const site = await SiteService.getSiteById("550e8400-...");
console.log("Site details:", site);
console.log("Storage used:", SiteService.formatStorage(site.storageUsedBytes));

Test Rotate API Key

const result = await SiteService.rotateApiKey("550e8400-...");
console.log("🚨 SAVE THIS NEW KEY NOW:", result.newApiKey);
console.log("Old key (immediately invalidated):", result.oldKeyLastFour);
console.log("Rotated at:", result.rotatedAt);

Test with curl

# 1. Login and get access token
ACCESS_TOKEN=$(curl -X POST http://localhost:8000/api/v1/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "password": "SecurePass123!"
  }' | jq -r '.access_token')

# 2. Create site
curl -X POST http://localhost:8000/api/v1/sites \
  -H "Content-Type: application/json" \
  -H "Authorization: JWT $ACCESS_TOKEN" \
  -d '{
    "domain": "example.com",
    "site_url": "https://example.com"
  }' | jq

# 3. List sites
curl -X GET "http://localhost:8000/api/v1/sites?page_size=10" \
  -H "Authorization: JWT $ACCESS_TOKEN" | jq

# 4. Get site by ID (use ID from creation response)
SITE_ID="550e8400-..."
curl -X GET "http://localhost:8000/api/v1/sites/$SITE_ID" \
  -H "Authorization: JWT $ACCESS_TOKEN" | jq

# 5. Rotate API key
curl -X POST "http://localhost:8000/api/v1/sites/$SITE_ID/rotate-api-key" \
  -H "Authorization: JWT $ACCESS_TOKEN" | jq

# 6. Delete site
curl -X DELETE "http://localhost:8000/api/v1/sites/$SITE_ID" \
  -H "Authorization: JWT $ACCESS_TOKEN" | jq

Important Notes

API Key Security

  1. API keys are shown only once - on site creation and API key rotation
  2. Store API keys securely immediately after receiving them
  3. Never log or display API keys in client-side code after initial display
  4. Use HTTPS for all API communications
  5. Rotate keys immediately if compromised

Site Status

  • pending: Site created but WordPress plugin not yet verified
  • active: Plugin successfully verified and connected

Sites remain in "pending" status until the WordPress plugin makes its first authenticated request using the verification token.

Hard Delete Warning

The deleteSite() operation is irreversible. All data is immediately and permanently deleted:

  • API keys are invalidated (plugin stops working immediately)
  • Search index is destroyed (all indexed content lost)
  • Usage statistics are deleted
  • Configuration is removed

There is no recovery or "soft delete" option.

Immediate Invalidation

When rotating an API key:

  • The new key is active immediately
  • The old key is immediately invalidated - no grace period!
  • Your WordPress site functionality stops working instantly
  • You must update the WordPress plugin configuration RIGHT NOW to restore functionality

Pagination

  • Default page size: 20 sites
  • Maximum page size: 100 sites
  • Use page_state token to get next page
  • When pageState is null, you've reached the last page

Storage Formatting

The formatStorage() helper formats bytes to human-readable strings:

  • 0 Bytes
  • 2 KB
  • 50 MB
  • 1.5 GB
  • 2.5 TB

Created Files

src/services/API/SiteService.js
docs/SITE_API.md

Backend Reference Files

cloud/maplepress-backend/docs/API.md (lines 805-1085)
cloud/maplepress-backend/internal/interface/http/site_http.go

Summary

The Site API implementation provides:

  1. Site Creation: Register WordPress sites with automatic API key generation
  2. Site Listing: Paginated list of all sites in tenant
  3. Site Details: Comprehensive site info with usage statistics
  4. API Key Rotation: Secure key rotation with immediate invalidation (no grace period)
  5. Site Deletion: Hard delete with immediate key invalidation
  6. Validation Helpers: Client-side validation before API calls
  7. Storage Formatting: Human-readable storage display

Essential for managing WordPress sites using the MaplePress plugin for cloud-powered search and other services.


Last Updated: October 30, 2024 Frontend Version: 0.0.0 Documentation Version: 1.0.0