Skip to content

Conversation

@hwakabh
Copy link
Owner

@hwakabh hwakabh commented Aug 3, 2025

Issue/PR link

N/A

What does this PR do?

Describe what changes you make in your branch:

  • added backend endpoint for fetching data from Tumblr API
  • enabled to render API response as raw HTML with using v-html
  • added Tumblr consumer key (API Key) to GitHub Action Secrets
  • updated swagger schema

(Optional) Additional Contexts

Describe additional information for reviewers (i.e. What does not included)
N/A

Summary by CodeRabbit

  • New Features

    • Introduced a new API endpoint to fetch blog posts dynamically from Tumblr.
    • Updated the blog page to display live blog content retrieved from the API instead of static placeholder text.
  • Documentation

    • Added documentation for the new blog posts API endpoint in the API reference.
  • Chores

    • Updated environment variable configuration for backend deployment.

@coderabbitai
Copy link

coderabbitai bot commented Aug 3, 2025

Walkthrough

This change introduces a dynamic blog feature by adding a new API route that fetches posts from the Tumblr API and exposes them at /api/v1/posts. The frontend Blog page is refactored from static content to dynamically render posts retrieved from this endpoint. Workflow environment variables are updated to support the Tumblr API integration.

Changes

Cohort / File(s) Change Summary
Workflow Environment Update
.github/workflows/backend.yaml
Replaces the RAPID_API_KEY environment variable with TUMBLR_CONSUMER_KEY in the workflow and updates the Cloud Run deployment to use the new variable.
API Route Registration
api-server/routes/api.js
Imports and mounts a new router for /api/v1/posts, enabling the new posts API endpoint.
New Tumblr Posts API Route
api-server/routes/logues.js
Adds an Express router that fetches posts from a Tumblr blog using an API key, processes the data, and returns it as JSON; includes Swagger documentation.
Frontend Blog Page Refactor
web-frontend/src/pages/Blog.vue
Removes static HTML, adds a Vue <script setup> that fetches blog posts from the backend API, and renders them dynamically in the template.
API Documentation Update
schemas/swagger.json
Adds a new GET endpoint /api/v1/posts/ under the "Logues" tag in the Swagger specification describing the dynamic blog content API.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15 minutes

Suggested labels

enhancement


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f17147 and 95a5920.

📒 Files selected for processing (1)
  • schemas/swagger.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • schemas/swagger.json
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/blog-integrations

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added ci Topics on workflows or automations app(api) Issue or PR related to backend API deploy Features depending on application runtimes app(ui) Issue or PR related to frontend UI labels Aug 3, 2025
@github-actions github-actions bot added the schemas Changes on schemas label Aug 3, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c5e168 and 4f17147.

📒 Files selected for processing (4)
  • .github/workflows/backend.yaml (2 hunks)
  • api-server/routes/api.js (1 hunks)
  • api-server/routes/logues.js (1 hunks)
  • web-frontend/src/pages/Blog.vue (1 hunks)
🔇 Additional comments (2)
api-server/routes/api.js (1)

8-8: LGTM! Router integration follows existing patterns.

The new posts router is properly imported and mounted following the established pattern in this file.

Also applies to: 16-16

.github/workflows/backend.yaml (1)

14-14: LGTM! Environment variable updates are consistent.

The replacement of RAPID_API_KEY with TUMBLR_CONSUMER_KEY is properly implemented in both the environment declaration and the Cloud Run deployment step.

Also applies to: 70-70

Comment on lines +10 to +18
const CONSUMER_KEY = process.env.TUMBLR_CONSUMER_KEY;
const url = 'https://api.tumblr.com/v2/blog/hwakabh.tumblr.com/posts?api_key=' + CONSUMER_KEY;
const respPosts = await axios.get(url)
.then(response => {
return response.data.response.posts
})
.catch(error => {
console.log(error);
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve error handling and API request structure.

Several issues with the current implementation:

  1. Missing error handling: If the Tumblr API fails, respPosts will be undefined, causing a crash on line 21.
  2. API key security: Concatenating the API key in the URL string is less secure than using query parameters.
  3. No error response: Errors are only logged but not returned to the client.

Apply this diff to improve error handling and security:

  const CONSUMER_KEY = process.env.TUMBLR_CONSUMER_KEY;
-  const url = 'https://api.tumblr.com/v2/blog/hwakabh.tumblr.com/posts?api_key=' + CONSUMER_KEY;
-  const respPosts = await axios.get(url)
-    .then(response => {
-      return response.data.response.posts
-    })
-    .catch(error => {
-      console.log(error);
-    })
+  
+  if (!CONSUMER_KEY) {
+    return res.status(500).json({ error: 'Tumblr API key not configured' });
+  }
+
+  try {
+    const response = await axios.get('https://api.tumblr.com/v2/blog/hwakabh.tumblr.com/posts', {
+      params: { api_key: CONSUMER_KEY }
+    });
+    
+    const respPosts = response.data.response.posts || [];

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In api-server/routes/logues.js around lines 10 to 18, improve error handling by
using try-catch instead of mixing await with then/catch, so you can handle
errors properly and avoid respPosts being undefined. Use axios with a params
object to pass the API key securely instead of concatenating it in the URL
string. In the catch block, log the error and send an appropriate error response
to the client to avoid silent failures.

Comment on lines +12 to +35
const respPosts = await axios.get(url)
.then(response => {
return response.data.response.posts
})
.catch(error => {
console.log(error);
})

const posts = [];
respPosts.forEach(p => {
posts.push({
date: p.date,
body: p.body
})
});

console.log(posts);

res.header('Content-Type', 'application/json; charset=utf-8');
res.json({
"path": req.originalUrl,
"content": posts
});

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add proper error handling for the entire route.

The route needs comprehensive error handling to prevent crashes and provide meaningful responses to clients.

Wrap the entire route logic in a try-catch block:

router.get('/', async (req, res, next) => {
  // #swagger.tags = ['Logues']
  // #swagger.summary = 'returns dynamic content for the blog page'
  // #swagger.description = '/api/v1/logues'

+  try {
    const CONSUMER_KEY = process.env.TUMBLR_CONSUMER_KEY;
    
    if (!CONSUMER_KEY) {
      return res.status(500).json({ error: 'Tumblr API key not configured' });
    }

    const response = await axios.get('https://api.tumblr.com/v2/blog/hwakabh.tumblr.com/posts', {
      params: { api_key: CONSUMER_KEY }
    });
    
    const respPosts = response.data.response.posts || [];
    const posts = [];
    
    if (respPosts && Array.isArray(respPosts)) {
      respPosts.forEach(p => {
        posts.push({
          date: p.date,
          body: p.body
        })
      });
    }

    res.header('Content-Type', 'application/json; charset=utf-8');
    res.json({
      "path": req.originalUrl,
      "content": posts
    });

+  } catch (error) {
+    console.error('Error fetching Tumblr posts:', error);
+    res.status(500).json({ 
+      error: 'Failed to fetch blog posts',
+      path: req.originalUrl 
+    });
+  }
});

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In api-server/routes/logues.js between lines 12 and 35, the route lacks
comprehensive error handling which can cause crashes and unclear client
responses. Wrap the entire route logic inside a try-catch block to catch any
errors during execution. In the catch block, log the error and send a meaningful
error response to the client with an appropriate HTTP status code, such as 500,
to indicate a server error.

Comment on lines +20 to +28
const posts = [];
respPosts.forEach(p => {
posts.push({
date: p.date,
body: p.body
})
});

console.log(posts);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove debug logging and add validation.

The current implementation has production code issues:

  1. Debug logging: console.log(posts) should be removed from production code.
  2. Missing validation: No check if respPosts exists before processing.

Apply this diff:

  const posts = [];
-  respPosts.forEach(p => {
-    posts.push({
-      date: p.date,
-      body: p.body
-    })
-  });
-
-  console.log(posts);
+  if (respPosts && Array.isArray(respPosts)) {
+    respPosts.forEach(p => {
+      posts.push({
+        date: p.date,
+        body: p.body
+      })
+    });
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const posts = [];
respPosts.forEach(p => {
posts.push({
date: p.date,
body: p.body
})
});
console.log(posts);
const posts = [];
if (respPosts && Array.isArray(respPosts)) {
respPosts.forEach(p => {
posts.push({
date: p.date,
body: p.body
})
});
}
🤖 Prompt for AI Agents
In api-server/routes/logues.js between lines 20 and 28, remove the debug
statement console.log(posts) to clean up production code and add a validation
check to ensure respPosts exists and is an array before iterating over it. This
prevents runtime errors if respPosts is undefined or null. Use a conditional
statement to verify respPosts is valid before the forEach loop.

Comment on lines +6 to +12
axios.get('/api/v1/posts')
.then((resp) => {
postData.value = resp.data.content;
})
.catch((err) => {
console.log(err);
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling and add loading state.

The current implementation lacks user feedback for loading and error states.

Apply this diff to improve user experience:

+const postData = ref([]);
+const loading = ref(true);
+const error = ref(null);

-const postData = ref([]);
-axios.get('/api/v1/posts')
-  .then((resp) => {
-    postData.value = resp.data.content;
-  })
-  .catch((err) => {
-    console.log(err);
-  })
+
+const fetchPosts = async () => {
+  try {
+    loading.value = true;
+    const resp = await axios.get('/api/v1/posts');
+    postData.value = resp.data.content || [];
+  } catch (err) {
+    console.error('Failed to fetch posts:', err);
+    error.value = 'Failed to load blog posts. Please try again later.';
+  } finally {
+    loading.value = false;
+  }
+};
+
+fetchPosts();

Then update the template to handle these states:

<template>
  <section class="wrapper fullscreen">
    <div class="inner">
      <h1 class="major">Blogs</h1>
+      <div v-if="loading">Loading posts...</div>
+      <div v-else-if="error" class="error">{{ error }}</div>
+      <div v-else-if="postData.length === 0">No posts available.</div>
       <div v-for="(p, idx) in postData"
         :key="idx"
         class="post-detail"
         >
🤖 Prompt for AI Agents
In web-frontend/src/pages/Blog.vue around lines 6 to 12, the current axios call
lacks loading and error state handling, which reduces user feedback. Introduce
reactive variables to track loading and error states, set loading to true before
the request, update postData on success, set error state on failure, and reset
loading after completion. Then update the template to display appropriate
messages or indicators based on these states.

Comment on lines +24 to +25
<div v-html="p.date"></div>
<div v-html="p.body"></div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

CRITICAL: XSS vulnerability with v-html on external content.

Using v-html to render content from external APIs (Tumblr) creates a serious XSS vulnerability. Malicious scripts in blog posts could execute in users' browsers.

Never use v-html with untrusted external content. Consider these safer alternatives:

Option 1: Text-only rendering (safest)

-            <div v-html="p.date"></div>
-            <div v-html="p.body"></div>
+            <div>{{ p.date }}</div>
+            <div>{{ p.body }}</div>

Option 2: Server-side sanitization (recommended)
Install and use DOMPurify on the backend to sanitize HTML before sending to frontend:

// In logues.js
const DOMPurify = require('isomorphic-dompurify');

posts.push({
  date: p.date,
  body: DOMPurify.sanitize(p.body)
});

Option 3: Client-side sanitization (fallback)

+import DOMPurify from 'dompurify';

-            <div v-html="p.body"></div>
+            <div v-html="DOMPurify.sanitize(p.body)"></div>
🤖 Prompt for AI Agents
In web-frontend/src/pages/Blog.vue around lines 24 to 25, using v-html to render
external content from Tumblr creates a critical XSS vulnerability. To fix this,
avoid using v-html directly with untrusted content. Instead, sanitize the HTML
on the server side before sending it to the frontend by integrating a library
like DOMPurify to clean the HTML in the backend code that fetches the posts.
Alternatively, if server-side sanitization is not possible, sanitize the content
on the client side before rendering. If neither sanitization is feasible, render
the content as plain text without HTML interpretation.

@hwakabh
Copy link
Owner Author

hwakabh commented Aug 3, 2025

Evidences in local environment:

25-08-03 23:17:08 git/hwakabh.github.io [feat/blog-integrations] % export TUMBLR_CONSUMER_KEY='xxx'
25-08-03 23:17:26 git/hwakabh.github.io [feat/blog-integrations] % make clean && make all                                                         
# ...
>>> [api-server] Starting up API server process
>>> [web-frontend] Starting up API server process
25-08-03 23:17:37 git/hwakabh.github.io [feat/blog-integrations] % 
> api-server@0.0.0 dev
> nodemon ./bin/www


> web-frontend@0.0.0 dev
> vite --clearScreen false

[nodemon] 3.1.10
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,cjs,json
[nodemon] starting `node ./bin/www`

  VITE v7.0.6  ready in 114 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
[
  {
    date: '2025-08-03 13:22:35 GMT',
    body: '<h2>Each title seems to be good with h2</h2><p>Another post here, without any title block.</p><p>another paragraph here.</p>'
  },
  {
    date: '2025-08-03 13:17:08 GMT',
    body: '<h2>Another title are here</h2><p>Another contents here.</p><p>With another posts.</p>'
  }
]

@hwakabh hwakabh merged commit 2c35db9 into main Aug 3, 2025
10 checks passed
@hwakabh hwakabh deleted the feat/blog-integrations branch August 3, 2025 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app(api) Issue or PR related to backend API app(ui) Issue or PR related to frontend UI ci Topics on workflows or automations deploy Features depending on application runtimes schemas Changes on schemas

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants