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

816 lines
18 KiB
Markdown

# 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](#architecture-overview)
2. [Migration Strategy](#migration-strategy)
3. [Using api-helper.js](#using-api-helperjs)
4. [Authentication Patterns](#authentication-patterns)
5. [Common Migration Patterns](#common-migration-patterns)
6. [Error Handling](#error-handling)
7. [Testing Integration](#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:
```javascript
// Available globally
const api = window.api;
// Or create new instance
const customAPI = new EasyStreamAPI('/api');
```
### Authentication
#### Login
```javascript
// 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
```javascript
if (api.isAuthenticated()) {
console.log('User is logged in');
} else {
console.log('User needs to login');
}
```
#### Logout
```javascript
await api.logout();
// Token is automatically cleared
```
### Making API Calls
#### Videos
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
### JWT Token Authentication (Recommended)
Best for: SPAs, mobile apps, API clients
```javascript
// 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
```javascript
// 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:
```javascript
// 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):**
```javascript
jQuery.get(url, function(result) {
console.log(result);
updateUI(result);
});
```
**After (api-helper):**
```javascript
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):**
```javascript
jQuery.post(url, {
field1: value1,
field2: value2
}, function(result) {
if (result.success) {
showSuccess('Saved!');
}
});
```
**After (api-helper):**
```javascript
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):**
```javascript
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):**
```javascript
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):**
```javascript
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):**
```javascript
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):**
```javascript
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):**
```javascript
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):**
```javascript
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):**
```javascript
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
```javascript
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
```javascript
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
```javascript
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/`:
```javascript
// 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:
```javascript
// 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](API_DOCUMENTATION.md) for endpoint details
2. Review [BACKEND_FRONTEND_INTEGRATION_FIXES.md](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