Story Collections Feature
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { AuthResponse, Story, Author, Tag, Series, SearchResult, PagedResult } from '../types/api';
|
||||
import { AuthResponse, Story, Author, Tag, Series, SearchResult, PagedResult, Collection, CollectionSearchResult, StoryWithCollectionContext, CollectionStatistics } from '../types/api';
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || '/api';
|
||||
|
||||
@@ -136,6 +136,11 @@ export const storyApi = {
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getStoryCollections: async (storyId: string): Promise<Collection[]> => {
|
||||
const response = await api.get(`/stories/${storyId}/collections`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
reindexTypesense: async (): Promise<{ success: boolean; message: string; count?: number; error?: string }> => {
|
||||
const response = await api.post('/stories/reindex-typesense');
|
||||
return response.data;
|
||||
@@ -307,6 +312,141 @@ export const configApi = {
|
||||
},
|
||||
};
|
||||
|
||||
// Collection endpoints
|
||||
export const collectionApi = {
|
||||
getCollections: async (params?: {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
search?: string;
|
||||
tags?: string[];
|
||||
archived?: boolean;
|
||||
}): Promise<CollectionSearchResult> => {
|
||||
// Create URLSearchParams to properly handle array parameters
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
if (params?.page !== undefined) searchParams.append('page', params.page.toString());
|
||||
if (params?.limit !== undefined) searchParams.append('limit', params.limit.toString());
|
||||
if (params?.search) searchParams.append('search', params.search);
|
||||
if (params?.archived !== undefined) searchParams.append('archived', params.archived.toString());
|
||||
|
||||
// Add array parameters - each element gets its own parameter
|
||||
if (params?.tags && params.tags.length > 0) {
|
||||
params.tags.forEach(tag => searchParams.append('tags', tag));
|
||||
}
|
||||
|
||||
const response = await api.get(`/collections?${searchParams.toString()}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getCollection: async (id: string): Promise<Collection> => {
|
||||
const response = await api.get(`/collections/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
createCollection: async (collectionData: {
|
||||
name: string;
|
||||
description?: string;
|
||||
tagNames?: string[];
|
||||
storyIds?: string[];
|
||||
}): Promise<Collection> => {
|
||||
const response = await api.post('/collections', collectionData);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
createCollectionWithImage: async (collectionData: {
|
||||
name: string;
|
||||
description?: string;
|
||||
tags?: string[];
|
||||
storyIds?: string[];
|
||||
coverImage?: File;
|
||||
}): Promise<Collection> => {
|
||||
const formData = new FormData();
|
||||
formData.append('name', collectionData.name);
|
||||
if (collectionData.description) formData.append('description', collectionData.description);
|
||||
if (collectionData.tags) {
|
||||
collectionData.tags.forEach(tag => formData.append('tags', tag));
|
||||
}
|
||||
if (collectionData.storyIds) {
|
||||
collectionData.storyIds.forEach(id => formData.append('storyIds', id));
|
||||
}
|
||||
if (collectionData.coverImage) {
|
||||
formData.append('coverImage', collectionData.coverImage);
|
||||
}
|
||||
|
||||
const response = await api.post('/collections', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
updateCollection: async (id: string, collectionData: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
tagNames?: string[];
|
||||
rating?: number;
|
||||
}): Promise<Collection> => {
|
||||
const response = await api.put(`/collections/${id}`, collectionData);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
deleteCollection: async (id: string): Promise<void> => {
|
||||
await api.delete(`/collections/${id}`);
|
||||
},
|
||||
|
||||
archiveCollection: async (id: string, archived: boolean): Promise<Collection> => {
|
||||
const response = await api.put(`/collections/${id}/archive`, { archived });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
addStoriesToCollection: async (id: string, storyIds: string[], position?: number): Promise<{
|
||||
added: number;
|
||||
skipped: number;
|
||||
totalStories: number;
|
||||
}> => {
|
||||
const response = await api.post(`/collections/${id}/stories`, {
|
||||
storyIds,
|
||||
position,
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
removeStoryFromCollection: async (collectionId: string, storyId: string): Promise<void> => {
|
||||
await api.delete(`/collections/${collectionId}/stories/${storyId}`);
|
||||
},
|
||||
|
||||
reorderStories: async (collectionId: string, storyOrders: Array<{
|
||||
storyId: string;
|
||||
position: number;
|
||||
}>): Promise<void> => {
|
||||
await api.put(`/collections/${collectionId}/stories/order`, {
|
||||
storyOrders,
|
||||
});
|
||||
},
|
||||
|
||||
getStoryWithCollectionContext: async (collectionId: string, storyId: string): Promise<StoryWithCollectionContext> => {
|
||||
const response = await api.get(`/collections/${collectionId}/read/${storyId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getCollectionStatistics: async (id: string): Promise<CollectionStatistics> => {
|
||||
const response = await api.get(`/collections/${id}/stats`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
uploadCover: async (id: string, coverImage: File): Promise<{ imagePath: string }> => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', coverImage);
|
||||
const response = await api.post(`/collections/${id}/cover`, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
removeCover: async (id: string): Promise<void> => {
|
||||
await api.delete(`/collections/${id}/cover`);
|
||||
},
|
||||
};
|
||||
|
||||
// Image utility
|
||||
export const getImageUrl = (path: string): string => {
|
||||
if (!path) return '';
|
||||
|
||||
Reference in New Issue
Block a user