Making posts' versions visible

Hi. This is the thirtieth part of the diary about developing the “Programmers’ diary” blog. Previous part: https://hashnode.programmersdiary.com/storing-data-in-minio-making-posts-editable. The open source code of this project is on: https://github.com/TheProgrammersDiary. The first diary entry explains why this project was done: https://medium.com/@vievaldas/developing-a-website-with-microservices-part-1-the-idea-fe6e0a7a96b5.
Next entry:
—————
2024-02-04
Final result
Yesterday Programmersdiary software got post editing functionality. Latest edit was visible.
From now on in addition to recent edit you can view post history. Also, comments will now include timestamps, so you can clearly see which version of the post a comment most likely refers to:
Next, you can click on the dropdown to select a specific version of the post:
If you are the owner of post when you select Edit the current version title and content will be placed in Edit window.
After submitting changes you will see this new version in the dropdown log.
Code changes
Before the changes we only needed id of post to retrieve it. Now we also need a version.
First we add a test:
Then we create a new endpoint:
Then we update the Post class:
With that and some smaller changes the test passes and the version control functionality is in place. Full commit of Post microservice: https://github.com/TheProgrammersDiary/Post/commit/5ef8c5fa334c6d37b589c97eb4fc11670d12dbf4.
The change also requires Frontend changes:
import React, { useEffect, useState } from 'react';
import { postUrl } from '../../../next.config';
export default function Dropdown({ postId, onValueChanged }) {
const [isOpen, setIsOpen] = useState(false);
const [selectedValue, setSelectedValue] = useState(null);
const [postVersions, setPostVersions] = useState(null);
const formatDate = (postedDate) => {
const date = new Date(postedDate);
const formattedDate = `${date.getFullYear()}-${padZero(date.getMonth() + 1)}-${padZero(date.getDate())} ${padZero(date.getHours())}:${padZero(date.getMinutes())}`;
return formattedDate;
};
const padZero = (value) => (value < 10 ? `0${value}` : value);
const valueChanged = (item) => {
setSelectedValue(item);
onValueChanged(item.version);
setIsOpen(false);
};
useEffect(() => {
if (postVersions && !selectedValue) {
valueChanged(postVersions[postVersions.length - 1]);
}
}, [postVersions]);
const toggleDropdown = () => {
setIsOpen(!isOpen);
};
useEffect(() => {
const fetchPostVersions = async () => {
try {
const response = await fetch(`${postUrl}/posts/${postId}/version`, {
method: 'GET',
credentials: 'omit',
});
const versions = await response.json();
setPostVersions(versions);
} catch (error) {
console.error('Error fetching post versions:', error);
}
};
fetchPostVersions();
}, [postId]);
return (
<div className="w-full relative inline-block text-left">
{selectedValue && (
<button
onClick={toggleDropdown}
type="button"
className="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
id="options-menu"
aria-haspopup="true"
aria-expanded="true"
>
{formatDate(selectedValue.datePosted)}
</button>
)}
{isOpen && (
<div
className="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu"
>
<div className="py-1" role="none">
{postVersions.map((item) => (
<a
key={item.version}
href="#"
className={`block px-4 py-2 text-sm ${
selectedValue === item
? 'bg-gray-100 text-gray-900'
: 'text-gray-700 hover:bg-gray-100 hover:text-gray-900'
}`}
onClick={() => valueChanged(item)}
role="menuitem"
>
{formatDate(item.datePosted)}
</a>
))}
</div>
</div>
)}
</div>
);
};
The Dropdown component is used in Post component:
"use client";
import Link from "next/link";
import "../../../globals.css";
import { useEffect, useState } from "react";
import { postUrl } from "../../../next.config.js";
import { useParams } from "next/navigation";
import Dropdown from "./dropdown";
export default function Post({ onArticleSet }) {
const [article, setArticle] = useState(null);
const [version, setVersion] = useState(null);
const [isOwner, setIsOwner] = useState("false");
const { postId } = useParams();
useEffect(() => {
if (!version) return;
const effect = async () => {
try {
const response = await fetch(`${postUrl}/posts/${postId}/${version}`, {
method: "GET",
credentials: "include",
});
if (response.status === 404) {
const text = await response.text();
throw new Error(text);
}
const responseData = await response.json();
setArticle(responseData);
onArticleSet();
setIsOwner(response.headers.get("IS-OWNER"));
} catch (error) {
// Error handling can be added here
}
};
effect();
}, [postId, version]);
return (
<>
<div className="flex justify-end mb-4">
<div className="w-full">
<Dropdown postId={postId} onValueChanged={(version) => setVersion(version)} />
</div>
</div>
{article && isOwner === "true" && (
<div className="mt-4 w-full">
<Link
href={`/post/create?postId=${postId}&version=${version}`}
className="w-full block px-6 py-3.5 text-base font-medium text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
Edit
</Link>
</div>
)}
{article && (
<>
<h1 className="block mt-1 text-lg leading-tight font-medium text-black">{article.title}</h1>
<p className="mt-2 text-gray-500">{article.author}</p>
<p className="mt-2 text-gray-600 break-all">{article.content}</p>
</>
)}
</>
);
}
These are the main changes which are required to make the version control shown at the start of the today’s blog.
Full Frontend changes: https://github.com/TheProgrammersDiary/Frontend/commit/ba017277f0ae2ec47e275ce1ac556e935132e344.
—————
Thanks for reading.
The project logged in this diary is open source, so if you would like to code or suggest changes, please visit https://github.com/TheProgrammersDiary.
Next part: https://hashnode.programmersdiary.com/securing-microservices-with-jwt-refresh-tokens.
Subscribe to my newsletter
Read articles from Evaldas Visockas directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
