- Implemented TourProgressController to handle API endpoints for tracking guided tours seen by users. - Created UserTourProgress entity to store the highest version of tours seen by each user. - Developed UserTourProgressRepository for database interactions related to user tour progress. - Introduced TourProgressService to manage business logic for marking tours as seen and retrieving seen maps. - Added comprehensive tests for API endpoints and entity behavior to ensure functionality and data integrity.
49 lines
2.1 KiB
TypeScript
49 lines
2.1 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
import { api } from '../lib/api';
|
|
import type { ApiResponse } from '../lib/api';
|
|
|
|
/** tour id => بالاترین نسخهای که کاربر دیده است */
|
|
type SeenMap = Record<string, number>;
|
|
|
|
export const TOURS_QUERY_KEY = ['my-tours'] as const;
|
|
|
|
/**
|
|
* وضعیت «دیدهشده» روی حساب کاربر ذخیره میشود نه روی مرورگر، تا با عوض کردن
|
|
* دستگاه یا مرورگر تورها دوباره از سر اجرا نشوند.
|
|
*/
|
|
export function useTourProgress() {
|
|
const queryClient = useQueryClient();
|
|
|
|
const query = useQuery({
|
|
queryKey: TOURS_QUERY_KEY,
|
|
queryFn: () => api.get<ApiResponse<{ seen: SeenMap }>>('/api/v1/my/tours'),
|
|
// در طول یک نشست عوض نمیشود مگر با همین mutation، پس refetch بیفایده است.
|
|
staleTime: Infinity,
|
|
});
|
|
|
|
const seen: SeenMap = query.data?.data?.seen ?? {};
|
|
|
|
const markSeen = useMutation({
|
|
mutationFn: ({ tourId, version }: { tourId: string; version: number }) =>
|
|
api.post<ApiResponse<{ tourId: string; version: number }>>(`/api/v1/my/tours/${tourId}/seen`, { version }),
|
|
// کش را بدون رفتوبرگشت اضافه بهروز میکند؛ سرور همین مقدار را برمیگرداند.
|
|
onSuccess: (_data, { tourId, version }) => {
|
|
queryClient.setQueryData<ApiResponse<{ seen: SeenMap }>>(TOURS_QUERY_KEY, (prev) => {
|
|
const previous = prev?.data?.seen ?? {};
|
|
return {
|
|
...(prev ?? { success: true, errors: [] as never[] }),
|
|
data: { seen: { ...previous, [tourId]: Math.max(previous[tourId] ?? 0, version) } },
|
|
} as ApiResponse<{ seen: SeenMap }>;
|
|
});
|
|
},
|
|
});
|
|
|
|
return {
|
|
seen,
|
|
/** تا وقتی پاسخ سرور نیامده، هیچ توری خودکار اجرا نمیشود */
|
|
isReady: query.isSuccess,
|
|
isSeen: (tourId: string, version: number) => (seen[tourId] ?? 0) >= version,
|
|
markSeen: (tourId: string, version: number) => markSeen.mutate({ tourId, version }),
|
|
};
|
|
}
|