Files
easystream-main/docs/FRONTEND_BACKEND_INTEGRATION_GUIDE.md
SamiAhmed7777 f0f346deb9
Some checks failed
EasyStream Test Suite / test (pull_request) Has been cancelled
EasyStream Test Suite / code-quality (pull_request) Has been cancelled
EasyStream Test Suite / integration-test (pull_request) Has been cancelled
Sync current dev state
2025-12-15 17:28:21 -08:00

18 KiB

Frontend-Backend Integration Guide

Overview

This guide explains how to properly connect EasyStream's frontend JavaScript code with the backend API endpoints. It covers migrating from legacy jQuery AJAX calls to modern fetch API using the provided api-helper.js client.

Table of Contents

  1. Architecture Overview
  2. Migration Strategy
  3. Using api-helper.js
  4. Authentication Patterns
  5. Common Migration Patterns
  6. Error Handling
  7. Testing Integration

Architecture Overview

Current State

EasyStream has three layers of API integration:

  1. Modern API Endpoints (/api/*.php)

    • RESTful design
    • JSON request/response
    • JWT + Session auth support
    • Proper error handling
  2. Modern Frontend Client (api-helper.js)

    • Fetch API based
    • Promise/async-await pattern
    • Automatic token management
    • Error handling helpers
  3. Legacy Frontend Code (jQuery AJAX calls)

    • Scattered across multiple files
    • Inconsistent patterns
    • Needs migration

Target State

All frontend code should use api-helper.js for consistency, maintainability, and better error handling.


Migration Strategy

Phase 1: Identify Legacy Code (COMPLETED)

Found legacy jQuery AJAX calls in:

  • f_scripts/fe/js/browse.init.js
  • f_scripts/fe/js/jquery.init.js
  • Various other frontend scripts

Phase 2: Create Modern API Endpoints (COMPLETED)

Created comprehensive RESTful APIs:

  • /api/videos.php - Video operations
  • /api/user.php - User profile operations
  • /api/comments.php - Comment operations
  • /api/subscriptions.php - Subscription operations
  • /api/auth.php - Authentication (already existed, enhanced)

Phase 3: Enhance Frontend Client (COMPLETED)

Enhanced api-helper.js with methods for all endpoints.

Phase 4: Migrate Legacy Code (IN PROGRESS)

Replace jQuery AJAX calls with modern fetch API using api-helper.js.

Phase 5: Test & Validate (PENDING)

Test all integrated endpoints end-to-end.


Using api-helper.js

Basic Setup

The API helper is automatically initialized on page load:

// Available globally
const api = window.api;

// Or create new instance
const customAPI = new EasyStreamAPI('/api');

Authentication

Login

// Login and store token
try {
  const result = await api.login('username', 'password');
  if (result.success) {
    console.log('Logged in as:', result.user.usr_user);
    // Token is automatically stored in localStorage
  }
} catch (error) {
  console.error('Login failed:', error.message);
}

Check Authentication

if (api.isAuthenticated()) {
  console.log('User is logged in');
} else {
  console.log('User needs to login');
}

Logout

await api.logout();
// Token is automatically cleared

Making API Calls

Videos

// List videos
const videos = await api.getVideos({
  page: 1,
  limit: 20,
  sort: 'popular',
  category: 'entertainment'
});

// Get single video
const video = await api.getVideo('123456');

// Search videos
const searchResults = await api.searchVideos('search query', { page: 1 });

// Create video
const newVideo = await api.createVideo({
  title: 'My Video',
  description: 'Video description',
  privacy: 'public'
});

// Update video
await api.updateVideo('123456', {
  file_title: 'Updated Title'
});

// Delete video
await api.deleteVideo('123456');

// Like video
await api.likeVideo('123456', 'like');

// Record view
await api.recordVideoView('123456');

// Watch later
await api.toggleWatchLater('123456');

User Profile

// Get current user's profile
const myProfile = await api.getMyProfile();

// Get another user's profile
const userProfile = await api.getUserProfile(123);

// Update profile
await api.updateProfile({
  usr_dname: 'New Name',
  usr_about: 'Updated bio'
});

// Upload avatar
const fileInput = document.getElementById('avatar-input');
const file = fileInput.files[0];
await api.uploadAvatar(file);

// Get user stats
const stats = await api.getUserStats();

// Get user's videos
const userVideos = await api.getUserVideos(123, { page: 1 });

Comments

// Get comments
const comments = await api.getComments('123456', {
  page: 1,
  sort: 'recent'
});

// Create comment
const newComment = await api.createComment(
  '123456', // file_key
  'This is my comment',
  null // parent_id (null for top-level comment)
);

// Reply to comment
const reply = await api.createComment(
  '123456',
  'This is a reply',
  456 // parent comment ID
);

// Update comment
await api.updateComment(789, 'Updated text');

// Delete comment
await api.deleteComment(789);

// Like comment
await api.likeComment(789);

// Report comment
await api.reportComment(789, 'Spam');

Subscriptions

// Get subscriptions
const subs = await api.getSubscriptions();

// Check if subscribed
const status = await api.checkSubscription(123);
if (status.data.is_subscribed) {
  console.log('Already subscribed');
}

// Subscribe
await api.subscribe(123);

// Unsubscribe
await api.unsubscribe(123);

// Get subscription feed
const feed = await api.getSubscriptionFeed({ page: 1 });

// Get subscribers
const subscribers = await api.getSubscribers(123);

Authentication Patterns

Best for: SPAs, mobile apps, API clients

// 1. Login to get token
const result = await api.login('username', 'password');

// 2. Token is automatically stored and used for subsequent requests
const profile = await api.getMyProfile();

// 3. Logout clears token
await api.logout();

Session-based Authentication

Best for: Traditional multi-page websites

// Login via form submission (handles CSRF automatically)
const formData = new FormData(loginForm);
const response = await fetch('/api/auth.php?action=login', {
  method: 'POST',
  body: formData,
  credentials: 'include' // Important for cookies
});

Hybrid Approach

Use sessions for page navigation, JWT for API calls:

// Check if user has session
const statusResponse = await fetch('/api/auth.php?action=status', {
  credentials: 'include'
});
const status = await statusResponse.json();

if (status.authenticated) {
  // User has session, optionally get JWT for API calls
  // (if needed for cross-origin requests)
}

Common Migration Patterns

Pattern 1: Simple GET Request

Before (jQuery):

jQuery.get(url, function(result) {
  console.log(result);
  updateUI(result);
});

After (api-helper):

try {
  const result = await api.get(url);
  console.log(result);
  updateUI(result.data);
} catch (error) {
  api.handleError(error);
}

Pattern 2: POST with Data

Before (jQuery):

jQuery.post(url, {
  field1: value1,
  field2: value2
}, function(result) {
  if (result.success) {
    showSuccess('Saved!');
  }
});

After (api-helper):

try {
  const result = await api.post(url, {
    field1: value1,
    field2: value2
  });

  if (result.success) {
    showSuccess('Saved!');
  }
} catch (error) {
  api.handleError(error);
}

Pattern 3: Load More / Pagination

Before (jQuery):

jQuery(".more-button").click(function() {
  var page = parseInt(jQuery(this).attr("rel-page"));
  var url = _rel + "?p=0&m=" + idnr + "&sort=" + type + "&page=" + page;

  jQuery.get(url, function(result) {
    jQuery("#list ul").append(result);
    jQuery(".more-button").attr("rel-page", page + 1);
  });
});

After (api-helper):

document.addEventListener('click', async (e) => {
  if (e.target.classList.contains('more-button')) {
    const page = parseInt(e.target.getAttribute('data-page'));

    try {
      const result = await api.getVideos({
        page: page,
        sort: currentSort,
        category: currentCategory
      });

      if (result.success) {
        appendVideos(result.data.videos);
        e.target.setAttribute('data-page', page + 1);

        // Hide button if no more pages
        if (page >= result.data.pagination.pages) {
          e.target.style.display = 'none';
        }
      }
    } catch (error) {
      api.handleError(error);
    }
  }
});

function appendVideos(videos) {
  const list = document.getElementById('video-list');
  videos.forEach(video => {
    const item = createVideoElement(video);
    list.appendChild(item);
  });
}

Pattern 4: Form Submission

Before (jQuery):

jQuery("#comment-form").submit(function(e) {
  e.preventDefault();

  var formData = jQuery(this).serialize();

  jQuery.post("/submit-comment.php", formData, function(result) {
    if (result.success) {
      addCommentToUI(result.comment);
      jQuery("#comment-form")[0].reset();
    }
  });
});

After (api-helper):

document.getElementById('comment-form').addEventListener('submit', async (e) => {
  e.preventDefault();

  const formData = new FormData(e.target);
  const commentText = formData.get('comment_text');
  const fileKey = formData.get('file_key');

  try {
    const result = await api.createComment(fileKey, commentText);

    if (result.success) {
      addCommentToUI(result.data);
      e.target.reset();
    }
  } catch (error) {
    api.handleError(error);
  }
});

Pattern 5: Watch Later Toggle

Before (jQuery):

jQuery(".watch_later_wrap").click(function() {
  var file_key = jQuery(this).attr("rel-key");
  var file_type = jQuery(this).attr("rel-type");
  var url = _rel + "?a=cb-watchadd&for=sort-" + file_type;
  var _this = jQuery(this);

  jQuery.post(url, {"fileid[0]": file_key}, function(result) {
    _this.find(".icon-clock")
      .removeClass("icon-clock")
      .addClass("icon-check");
    _this.next().text("In Watch List");
  });
});

After (api-helper):

document.addEventListener('click', async (e) => {
  const watchBtn = e.target.closest('.watch-later-btn');
  if (!watchBtn) return;

  const fileKey = watchBtn.dataset.fileKey;

  try {
    const result = await api.toggleWatchLater(fileKey);

    if (result.success) {
      const icon = watchBtn.querySelector('.icon');
      const text = watchBtn.querySelector('.text');

      if (result.data.action === 'added') {
        icon.classList.remove('icon-clock');
        icon.classList.add('icon-check');
        text.textContent = 'In Watch List';
      } else {
        icon.classList.remove('icon-check');
        icon.classList.add('icon-clock');
        text.textContent = 'Watch Later';
      }
    }
  } catch (error) {
    api.handleError(error);
  }
});

Pattern 6: Subscribe Button

Before (jQuery):

jQuery(".subscribe-btn").click(function() {
  var channelId = jQuery(this).data("channel-id");

  jQuery.post("/subscribe.php", { channel_id: channelId }, function(result) {
    if (result.success) {
      jQuery(".subscribe-btn").text("Subscribed");
      jQuery(".subscriber-count").text(result.subscriber_count);
    }
  });
});

After (api-helper):

document.addEventListener('click', async (e) => {
  const subscribeBtn = e.target.closest('.subscribe-btn');
  if (!subscribeBtn) return;

  const channelId = parseInt(subscribeBtn.dataset.channelId);
  const isSubscribed = subscribeBtn.classList.contains('subscribed');

  try {
    if (isSubscribed) {
      await api.unsubscribe(channelId);
      subscribeBtn.classList.remove('subscribed');
      subscribeBtn.textContent = 'Subscribe';
    } else {
      const result = await api.subscribe(channelId);
      subscribeBtn.classList.add('subscribed');
      subscribeBtn.textContent = 'Subscribed';

      // Update subscriber count if available
      if (result.data.subscriber_count) {
        document.querySelector('.subscriber-count').textContent =
          result.data.subscriber_count;
      }
    }
  } catch (error) {
    api.handleError(error);
  }
});

Error Handling

Comprehensive Error Handling

async function performAction() {
  try {
    // Show loading state
    showLoading();

    const result = await api.someAction();

    if (result.success) {
      showSuccess('Action completed!');
      updateUI(result.data);
    } else {
      showError(result.error || 'Action failed');
    }

  } catch (error) {
    // Handle different error types
    if (error.message.includes('Authentication')) {
      // Redirect to login
      window.location.href = '/signin';
    } else if (error.message.includes('Network')) {
      showError('Network error. Please check your connection.');
    } else {
      showError(error.message || 'An unexpected error occurred');
    }

    // Log error for debugging
    console.error('Action failed:', error);

  } finally {
    // Always hide loading state
    hideLoading();
  }
}

Using Built-in Error Handler

try {
  const result = await api.someAction();
  // Handle success
} catch (error) {
  // Use built-in error handler with custom callback
  api.handleError(error, (message) => {
    showNotification('error', message);
  });
}

Retry Logic

async function apiCallWithRetry(apiMethod, maxRetries = 3) {
  let lastError;

  for (let i = 0; i < maxRetries; i++) {
    try {
      return await apiMethod();
    } catch (error) {
      lastError = error;

      // Don't retry on auth errors
      if (error.message.includes('Authentication')) {
        throw error;
      }

      // Wait before retry (exponential backoff)
      if (i < maxRetries - 1) {
        await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
      }
    }
  }

  throw lastError;
}

// Usage
try {
  const result = await apiCallWithRetry(() => api.getVideos({ page: 1 }));
} catch (error) {
  api.handleError(error);
}

Testing Integration

Manual Testing Checklist

  1. Authentication

    • Login with username
    • Login with email
    • Invalid credentials show error
    • Token persists after page reload
    • Logout clears token
    • Expired token triggers re-login
  2. Videos

    • List videos loads correctly
    • Pagination works
    • Sorting works (popular, recent, etc.)
    • Category filtering works
    • Single video loads with all details
    • Search returns relevant results
    • Like/dislike updates count
    • View count increments
    • Watch later toggle works
  3. User Profile

    • Profile loads correctly
    • Profile update saves changes
    • Avatar upload works
    • Statistics display correctly
    • User's videos load
  4. Comments

    • Comments load for video
    • Create comment works
    • Reply to comment works
    • Edit comment works
    • Delete comment works
    • Like comment updates count
    • Pagination works
  5. Subscriptions

    • Subscribe button works
    • Unsubscribe works
    • Subscription list displays
    • Subscriber count updates
    • Subscription feed loads

Automated Testing

Create test files in tests/integration/:

// Example: test-videos-api.js
describe('Videos API Integration', () => {
  let api;
  let testVideoId;

  beforeAll(async () => {
    api = new EasyStreamAPI('/api');
    await api.login('testuser', 'testpass');
  });

  test('should list videos', async () => {
    const result = await api.getVideos({ page: 1 });
    expect(result.success).toBe(true);
    expect(result.data.videos).toBeInstanceOf(Array);
  });

  test('should create video', async () => {
    const result = await api.createVideo({
      title: 'Test Video',
      description: 'Test description'
    });
    expect(result.success).toBe(true);
    testVideoId = result.data.file_key;
  });

  test('should get single video', async () => {
    const result = await api.getVideo(testVideoId);
    expect(result.success).toBe(true);
    expect(result.data.file_key).toBe(testVideoId);
  });

  afterAll(async () => {
    if (testVideoId) {
      await api.deleteVideo(testVideoId);
    }
    await api.logout();
  });
});

Browser Console Testing

Quick tests in browser console:

// Test login
await api.login('username', 'password')

// Test get videos
await api.getVideos({ page: 1 })

// Test create comment
await api.createComment('123456', 'Test comment')

// Check authentication
api.isAuthenticated()

// View stored token
localStorage.getItem('jwt_token')

Best Practices

  1. Always use try-catch for async API calls
  2. Show loading states during API requests
  3. Provide user feedback for success/error
  4. Cache results when appropriate
  5. Debounce search/autocomplete requests
  6. Validate input before sending to API
  7. Handle edge cases (empty results, network errors, etc.)
  8. Log errors for debugging
  9. Use TypeScript for better type safety (optional)
  10. Test thoroughly before deploying

Migration Priority

Migrate in this order:

  1. Critical User Actions

    • Login/Logout
    • Video playback
    • Comments
    • Subscriptions
  2. Content Display

    • Video listings
    • User profiles
    • Search
  3. Secondary Features

    • Notifications
    • Watch later
    • Playlists
  4. Admin Features

    • Analytics
    • Moderation
    • Settings

Support

If you encounter issues during migration:

  1. Check API_DOCUMENTATION.md for endpoint details
  2. Review BACKEND_FRONTEND_INTEGRATION_FIXES.md
  3. Test endpoints using browser DevTools Network tab
  4. Check backend logs for errors
  5. Verify CORS configuration if cross-origin issues occur

Last Updated: January 2025