feat: add TourProgressController and related entities for user tour progress tracking
- 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.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
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 }),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user