- Added Blog and BlogPost models with necessary fields and relationships. - Created blogRouter for handling blog-related API endpoints including CRUD operations. - Developed BlogService for business logic related to blogs and posts, including sharing functionality. - Implemented API client methods for frontend to interact with blog-related endpoints. - Added internationalization support for blog-related text in English and German. - Created Vue components for blog editing, listing, and viewing, including a rich text editor for post content. - Enhanced user experience with form validations and dynamic visibility settings based on user input.
175 lines
6.1 KiB
JavaScript
175 lines
6.1 KiB
JavaScript
import BlogService from '../services/blogService.js';
|
|
import SocialNetworkService from '../services/socialnetworkService.js';
|
|
|
|
const blogService = new BlogService();
|
|
const socialService = new SocialNetworkService();
|
|
|
|
export const createBlog = async (req, res) => {
|
|
try {
|
|
const { userid } = req.headers; // creator must be logged in
|
|
const blog = await blogService.createBlog(userid, req.body || {});
|
|
res.json(blog);
|
|
} catch (e) {
|
|
res.status(e.status || 400).json({ error: e.message });
|
|
}
|
|
};
|
|
|
|
export const updateBlog = async (req, res) => {
|
|
try {
|
|
const { userid } = req.headers;
|
|
const { id } = req.params;
|
|
const blog = await blogService.updateBlog(userid, id, req.body || {});
|
|
res.json(blog);
|
|
} catch (e) {
|
|
res.status(e.status || 400).json({ error: e.message });
|
|
}
|
|
};
|
|
|
|
export const listBlogs = async (req, res) => {
|
|
try {
|
|
const { userid } = req.headers; // optional for access checks
|
|
const blogs = await blogService.listBlogsForViewer(userid || null);
|
|
res.json(blogs);
|
|
} catch (e) {
|
|
res.status(400).json({ error: e.message });
|
|
}
|
|
};
|
|
|
|
export const getBlog = async (req, res) => {
|
|
try {
|
|
const { userid } = req.headers;
|
|
const { id } = req.params;
|
|
const blog = await blogService.getBlog(id, userid || null);
|
|
res.json(blog);
|
|
} catch (e) {
|
|
res.status(e.status || 404).json({ error: e.message });
|
|
}
|
|
};
|
|
|
|
export const createPost = async (req, res) => {
|
|
try {
|
|
const { userid } = req.headers;
|
|
const { id } = req.params;
|
|
const post = await blogService.createPost(userid, id, req.body || {});
|
|
res.json(post);
|
|
} catch (e) {
|
|
res.status(e.status || 400).json({ error: e.message });
|
|
}
|
|
};
|
|
|
|
export const listPosts = async (req, res) => {
|
|
try {
|
|
const { userid } = req.headers;
|
|
const { id } = req.params;
|
|
const page = Math.max(1, parseInt(req.query.page || '1', 10));
|
|
const pageSize = Math.min(50, Math.max(1, parseInt(req.query.pageSize || '10', 10)));
|
|
const all = await blogService.listPosts(id, userid || null);
|
|
const total = all.length;
|
|
const start = (page - 1) * pageSize;
|
|
const posts = all.slice(start, start + pageSize);
|
|
res.json({ items: posts, page, pageSize, total });
|
|
} catch (e) {
|
|
res.status(e.status || 400).json({ error: e.message });
|
|
}
|
|
};
|
|
|
|
export const listBlogImages = async (req, res) => {
|
|
try {
|
|
const { userid } = req.headers;
|
|
const user = await blogService.getUserByHashedId(userid);
|
|
const blogFolder = await blogService.ensureBlogFolder(user.id);
|
|
const images = await socialService.getFolderImageList(userid, blogFolder.id);
|
|
res.json({ folderId: blogFolder.id, images });
|
|
} catch (e) {
|
|
res.status(400).json({ error: e.message });
|
|
}
|
|
};
|
|
|
|
export const uploadBlogImage = async (req, res) => {
|
|
try {
|
|
const { userid } = req.headers;
|
|
const user = await blogService.getUserByHashedId(userid);
|
|
const blogFolder = await blogService.ensureBlogFolder(user.id);
|
|
// Default visibility mirrors blog folder visibilities (already applied)
|
|
const formData = {
|
|
title: req.body?.title || 'Blog Image',
|
|
description: req.body?.description || '',
|
|
folderId: blogFolder.id,
|
|
visibility: JSON.stringify([])
|
|
};
|
|
const file = req.file;
|
|
const image = await socialService.uploadImage(userid, file, formData);
|
|
res.status(201).json(image);
|
|
} catch (e) {
|
|
res.status(400).json({ error: e.message });
|
|
}
|
|
};
|
|
|
|
export const getPublicBlogImageByHash = async (req, res) => {
|
|
try {
|
|
const { hash } = req.params;
|
|
const filePath = await socialService.getImageFilePathPublicByHash(hash);
|
|
res.sendFile(filePath, err => {
|
|
if (err) {
|
|
console.error('Error sending file:', err);
|
|
res.status(500).json({ error: 'Error sending file' });
|
|
}
|
|
});
|
|
} catch (e) {
|
|
res.status(e.status || 403).json({ error: e.message || 'Access denied or image not found' });
|
|
}
|
|
};
|
|
|
|
export const shareBlog = async (req, res) => {
|
|
try {
|
|
const { userid } = req.headers;
|
|
const { id } = req.params;
|
|
const { toFriends = false, emails = [] } = req.body || {};
|
|
// Prefer configured frontend base for public links; fallback to request origin
|
|
const feBase = process.env.FRONTEND_BASE_URL;
|
|
const proto = (req.headers['x-forwarded-proto'] || req.protocol || 'http').split(',')[0].trim();
|
|
const host = (req.headers['x-forwarded-host'] || req.get('host'));
|
|
const origin = (feBase && feBase.trim()) ? feBase.replace(/\/$/, '') : `${proto}://${host}`;
|
|
|
|
// Compute slug from username + title (no spaces, remove unsafe chars)
|
|
const { user, blog } = await blogService.assertOwner(userid, id);
|
|
const titleNoSpaces = (blog.title || '').toString().replace(/\s+/g, '');
|
|
const base = `${user.username || 'user'}${titleNoSpaces}`;
|
|
const slug = base.replace(/[^a-zA-Z0-9_-]/g, '');
|
|
// Pretty public URL under frontend: /blogs/:slug
|
|
const url = `${origin}/blogs/${encodeURIComponent(slug)}`;
|
|
|
|
const result = await blogService.shareBlog(userid, id, { toFriends, emails }, url);
|
|
res.json(result);
|
|
} catch (e) {
|
|
res.status(e.status || 400).json({ error: e.message });
|
|
}
|
|
};
|
|
|
|
export const resolveBlogSlug = async (req, res) => {
|
|
try {
|
|
const { slug } = req.params;
|
|
const blogId = await blogService.resolveSlug(slug);
|
|
if (!blogId) return res.status(404).json({ error: 'Not found' });
|
|
// Redirect to SPA history route so the client router loads the blog
|
|
const feBase = process.env.FRONTEND_BASE_URL;
|
|
const proto = (req.headers['x-forwarded-proto'] || req.protocol || 'http').split(',')[0].trim();
|
|
const host = (req.headers['x-forwarded-host'] || req.get('host'));
|
|
const origin = (feBase && feBase.trim()) ? feBase.replace(/\/$/, '') : `${proto}://${host}`;
|
|
return res.redirect(302, `${origin}/blogs/${blogId}/${encodeURIComponent(slug)}`);
|
|
} catch (e) {
|
|
res.status(400).json({ error: e.message });
|
|
}
|
|
};
|
|
|
|
export const resolveBlogSlugId = async (req, res) => {
|
|
try {
|
|
const { slug } = req.params;
|
|
const blogId = await blogService.resolveSlug(slug);
|
|
if (!blogId) return res.status(404).json({ error: 'Not found' });
|
|
res.json({ id: blogId });
|
|
} catch (e) {
|
|
res.status(400).json({ error: e.message });
|
|
}
|
|
};
|