Files
www.cialloo.com/src/blog/api.ts
cialloo e299694f22
All checks were successful
CI - Build and Push / Build and Push Docker Image (push) Successful in 18s
feat: add toolbar styles and functionality for blog editor
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
2025-10-25 21:03:58 +08:00

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();
}