All checks were successful
CI - Build and Push / Build and Push Docker Image (push) Successful in 18s
feat: implement EditorTheme for consistent styling across editor components feat: define types for blog-related operations including image uploads and post creation feat: create DropdownColorPicker component for color selection in blog editor feat: implement ImageResizer component for resizing images in the blog editor feat: add export and import functionality for blog posts in JSON format feat: update main application routes to include CreatePost page feat: enhance Blog page with a button to navigate to CreatePost feat: implement CreatePost page with title, cover image upload, and content editor
129 lines
3.0 KiB
TypeScript
129 lines
3.0 KiB
TypeScript
/**
|
|
* Blog API functions
|
|
*/
|
|
|
|
import { apiRequest } from '../utils/api';
|
|
import type {
|
|
UploadPresignedURLResponse,
|
|
DownloadPresignedURLResponse,
|
|
CreatePostResponse,
|
|
} from './types';
|
|
|
|
const API_BASE = '/api/blog';
|
|
|
|
/**
|
|
* Get presigned URL for file upload
|
|
*/
|
|
export async function getUploadPresignedURL(fileName: string): Promise<UploadPresignedURLResponse> {
|
|
const response = await apiRequest(`${API_BASE}/file/upload`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ fileName }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to get upload URL: ${response.statusText}`);
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
/**
|
|
* Get presigned URL for file download
|
|
*/
|
|
export async function getDownloadPresignedURL(fileKey: string): Promise<DownloadPresignedURLResponse> {
|
|
const response = await apiRequest(`${API_BASE}/file/download`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ fileKey }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to get download URL: ${response.statusText}`);
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
/**
|
|
* Upload file to S3 using presigned URL
|
|
*/
|
|
export async function uploadFileToS3(
|
|
url: string,
|
|
file: File,
|
|
onProgress?: (progress: number) => void
|
|
): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
// Track upload progress
|
|
xhr.upload.addEventListener('progress', (event) => {
|
|
if (event.lengthComputable && onProgress) {
|
|
const progress = (event.loaded / event.total) * 100;
|
|
onProgress(progress);
|
|
}
|
|
});
|
|
|
|
xhr.addEventListener('load', () => {
|
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
resolve();
|
|
} else {
|
|
reject(new Error(`Upload failed with status ${xhr.status}`));
|
|
}
|
|
});
|
|
|
|
xhr.addEventListener('error', () => {
|
|
reject(new Error('Upload failed'));
|
|
});
|
|
|
|
xhr.addEventListener('abort', () => {
|
|
reject(new Error('Upload aborted'));
|
|
});
|
|
|
|
xhr.open('PUT', url);
|
|
xhr.setRequestHeader('Content-Type', file.type);
|
|
xhr.send(file);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Complete image upload workflow
|
|
*/
|
|
export async function uploadImage(
|
|
file: File,
|
|
onProgress?: (progress: number) => void
|
|
): Promise<{ fileKey: string; url: string }> {
|
|
// Step 1: Get presigned URL
|
|
const { url: uploadUrl, fileKey } = await getUploadPresignedURL(file.name);
|
|
|
|
// Step 2: Upload file to S3
|
|
await uploadFileToS3(uploadUrl, file, onProgress);
|
|
|
|
// Step 3: Get download URL
|
|
const { url: downloadUrl } = await getDownloadPresignedURL(fileKey);
|
|
|
|
return { fileKey, url: downloadUrl };
|
|
}
|
|
|
|
/**
|
|
* Create a new blog post
|
|
*/
|
|
export async function createBlogPost(
|
|
title: string,
|
|
content: string,
|
|
coverImageKey: string
|
|
): Promise<CreatePostResponse> {
|
|
const response = await apiRequest(`${API_BASE}/post/create`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
title,
|
|
content,
|
|
coverImageKey,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to create post: ${response.statusText}`);
|
|
}
|
|
|
|
return response.json();
|
|
}
|