Story Listing

Overview

We’ll break this into two parts:

  1. Frontend JavaScript: Handling story display, user authentication, and session management.
  2. Backend API: Providing data through a REST API for managing stories.

Step 1: Frontend JavaScript to Load and Display Stories

First, let’s set up the frontend JavaScript to handle displaying stories and managing user authentication.

1.1 Encoding and Decoding Story Titles

We need to safely pass story titles in URLs, so we’ll use encoding and decoding functions.

// Function to encode the story title for use in the URL, replacing spaces with '+'
function encodeTitleForURL(title) {
return title.replace(/\s+/g, "+");
}

// Function to decode the story title from the URL, replacing '+' with spaces
function decodeTitleFromURL(encoded) {
return encoded.replace(/\+/g, " ");
}

1.2 Handling User Authentication

We’ll create a function that updates the authentication buttons based on whether the user is logged in.

function updateAuthButtons() {
const authButtonsContainer = document.getElementById("auth-buttons");
const token = localStorage.getItem("token");

authButtonsContainer.innerHTML = "";

if (token) {
authButtonsContainer.innerHTML = `
<a href="myaccount" class="btn">My Profile</a>
<a href="mydrafts" class="btn">My Drafts</a>
<a href="mystories" class="btn">My Stories</a>
<a href="#" class="btn" onclick="logout()">Logout</a>
`;
} else {
authButtonsContainer.innerHTML = `
<a href="login" class="btn">Login</a>
<a href="register" class="btn">Register</a>
`;
}
}

1.3 Session Check and Logout

We need to manage the user’s session. This includes checking the token’s expiration and allowing the user to log out.

function logout() {
localStorage.removeItem("token");
alert("Logged out successfully!");
updateAuthButtons();
loadStories(); // Reload stories after logout to update UI
}

function checkSession() {
const token = localStorage.getItem("token");

// Handle token expiration logic
if (token) {
const tokenPayload = JSON.parse(atob(token.split(".")[1]));
const tokenExpiration = tokenPayload.exp * 1000;

if (Date.now() >= tokenExpiration) {
localStorage.removeItem("token");
}
}

updateAuthButtons(); // Update UI on session check
}

1.4 Loading Stories

Now, we’ll implement the logic to load stories from the backend API. We’ll check if the user is premium and adjust the story content accordingly.

async function loadStories() {
const token = localStorage.getItem("token");
let isPremiumUser = false;

// Check if the user is premium
if (token) {
const tokenPayload = JSON.parse(atob(token.split(".")[1]));
isPremiumUser = tokenPayload.isPremium || false;
}

try {
const response = await fetch("/api/stories");
const stories = await response.json();
const storyList = document.querySelector("#story-list");
const defaultContent = document.getElementById("default-content");
const adImage = document.getElementById("ad-image");
const buyButton = document.getElementById("buy-button");

// Clear existing stories before adding new ones
storyList.innerHTML = "";

if (stories.length > 0) {
// Hide default content if there are stories
if (defaultContent) defaultContent.style.display = "none";

// Hide ad content when loading default content or no story is selected
adImage.style.display = "none"; // Hide ad image when stories are available
buyButton.style.display = "none"; // Hide buy button if there are stories

stories.forEach((story) => {
const storyItem = document.createElement("li");
storyItem.innerHTML = `
<a href="#" class="story-link" data-title="${encodeTitleForURL(story.title)}">${story.title}</a>
`;
storyList.appendChild(storyItem);
});

// Optionally, load the first story's content for preview
const firstStory = stories[0];
displayStoryContent(firstStory);
} else {
// Show default content if no stories are available
if (defaultContent) defaultContent.style.display = "block";

// Show the default image as ad content when no stories are loaded
adImage.style.display = "none"; // Hide ad image during default content
buyButton.style.display = "none"; // Hide the buy button during default content
}
} catch (error) {
console.error("Error loading stories:", error);

// In case of an error (e.g., no DB connection), show the default content
const adImage = document.getElementById("ad-image");
const buyButton = document.getElementById("buy-button");

// Display the default content and hide ad content
if (defaultContent) defaultContent.style.display = "block";
adImage.style.display = "none";
buyButton.style.display = "none";
}
}

1.5 Displaying Story Content

When a user clicks on a story, we’ll display its content. If the story is marked as premium and the user is not premium, we’ll truncate the content.

function displayStoryContent(story) {
const publishedDate = new Date(story.publishedOn).toLocaleDateString();
document.getElementById("story-title").textContent = story.title;

// Check if the story is premium
const token = localStorage.getItem("token");
let isPremiumUser = false;
if (token) {
const tokenPayload = JSON.parse(atob(token.split(".")[1]));
isPremiumUser = tokenPayload.isPremium || false;
}

// Display content only if the user is premium or the story is not premium
let displayContent = story.content;
if (!isPremiumUser && story.isPremiumContent) {
// For non-premium users, truncate the content to half
displayContent = story.content.slice(0, Math.floor(story.content.length / 2));
document.getElementById("story-content").innerHTML = `
<p>${displayContent}</p>
<p><strong><a href="subscriptions.html"> Please subscribe to read more.</strong></p>
`;
return; // Return early to prevent showing the full content for non-premium users
}

// If it's not a premium story, or the user is premium, show full content
document.getElementById("story-content").innerHTML = `
<p class="submitted-info">
<sub>Submitted By: ${story.authorFirstName} ${story.authorLastName} | Published on: ${publishedDate}</sub>
</p>
<p>${displayContent}</p>
`;

setAdContent(story);
}

1.6 Setting Up Ad Content

If the story has an associated buy image and link, we will display those as ads.

function setAdContent(story) {
const adImage = document.getElementById("ad-image");
const buyButton = document.getElementById("buy-button");

if (story.buyImage && story.buyLink) {
adImage.src = story.buyImage;
buyButton.href = story.buyLink;
buyButton.style.display = "inline-block";
adImage.style.display = "block"; // Show the ad image
} else {
// Hide ad image and buy button if there are no valid links
adImage.style.display = "none";
buyButton.style.display = "none";
}
}

Step 2: Backend API for Managing Stories

Now, let’s set up the backend API to fetch stories from the database and handle user-specific content.

2.1 Fetching All Stories

This API endpoint will return all published stories, and it will handle modifying content for non-premium users.

// API to get all stories
app.get("/api/stories", async (req, res) => {
const userId = req.user?.id; // Get user info from the JWT token or session
const isPremiumUser = req.user?.isPremium || false; // Assuming 'isPremium' is stored in the JWT

const query = "SELECT * FROM stories WHERE published = TRUE"; // Get all published stories

try {
const results = await executeQuery(query);

// Modify the story content for non-premium users if it's a premium story
const modifiedResults = results.map((story) => {
if (!isPremiumUser && story.isPremiumContent) {
// Truncate the content for non-premium users
const halfContent = Math.floor(story.content.length / 2);
story.content = story.content.slice(0, halfContent); // Keep only the first half of the content
}

// Include the premium status flag (for the frontend to know)
return {
...story,
isPremiumContent: story.isPremiumContent, // Maintain isPremiumContent flag
};
});

// Send the modified stories list as a response
res.json(modifiedResults);

logWithTimestamp("Fetched all stories");
} catch (err) {
logWithTimestamp(`Error fetching stories: ${err}`);
res.status(500).json({ message: "Internal server error." });
}
});

2.2 Fetching a Specific Story

This API endpoint will fetch a specific story by its ID.

javascriptCopyEdit// Get a specific story by ID
app.get("/api/stories/:id", async (req, res) => {
  const { id } = req.params;
  const query = "SELECT * FROM stories WHERE id = ?";
  try {
    const results = await executeQuery(query, [id]);
    if (results.length === 0) {
      logWithTimestamp(`Story not found: ID ${id}`);
      return res.status(404).json({ message: "Story not found" });
    }
    res.json(results[0]);
    logWithTimestamp(`Fetched story ID ${id}`);
  } catch (err) {
    logWithTimestamp(`Error fetching story ID ${id}: ${err}`);
    return res.status(500).json({ error: err.message });
  }
});