posts are done
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Generated by orval v8.8.0 🍺
|
||||
* Do not edit manually.
|
||||
* OpenAPI spec version: 0.0.0
|
||||
*/
|
||||
|
||||
export type ApiSocialPostsSaveCreate200 = {
|
||||
saved?: boolean;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Generated by orval v8.8.0 🍺
|
||||
* Do not edit manually.
|
||||
* OpenAPI spec version: 0.0.0
|
||||
*/
|
||||
|
||||
export type ApiSocialPostsSavedListParams = {
|
||||
author?: number;
|
||||
hub?: number;
|
||||
/**
|
||||
* Which field to use when ordering the results.
|
||||
*/
|
||||
ordering?: string;
|
||||
/**
|
||||
* A page number within the paginated result set.
|
||||
*/
|
||||
page?: number;
|
||||
reply_to?: number;
|
||||
/**
|
||||
* A search term.
|
||||
*/
|
||||
search?: string;
|
||||
};
|
||||
@@ -29,6 +29,8 @@ export * from "./apiSocialMessagesListParams";
|
||||
export * from "./apiSocialPostsFeedListParams";
|
||||
export * from "./apiSocialPostsListParams";
|
||||
export * from "./apiSocialPostsMediaCreateBody";
|
||||
export * from "./apiSocialPostsSaveCreate200";
|
||||
export * from "./apiSocialPostsSavedListParams";
|
||||
export * from "./apiZasilkovnaShipmentsListParams";
|
||||
export * from "./authorMinimal";
|
||||
export * from "./callback";
|
||||
|
||||
@@ -23,4 +23,6 @@ export interface PatchedPost {
|
||||
readonly vote_score?: string;
|
||||
readonly user_vote?: string;
|
||||
readonly reply_count?: number;
|
||||
readonly is_saved?: string;
|
||||
readonly save_count?: string;
|
||||
}
|
||||
|
||||
@@ -23,4 +23,6 @@ export interface Post {
|
||||
readonly vote_score: string;
|
||||
readonly user_vote: string;
|
||||
readonly reply_count: number;
|
||||
readonly is_saved: string;
|
||||
readonly save_count: string;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import type {
|
||||
ApiSocialPostsFeedListParams,
|
||||
ApiSocialPostsListParams,
|
||||
ApiSocialPostsMediaCreateBody,
|
||||
ApiSocialPostsSaveCreate200,
|
||||
ApiSocialPostsSavedListParams,
|
||||
PaginatedPostList,
|
||||
PatchedPost,
|
||||
Post,
|
||||
@@ -806,6 +808,94 @@ export const useApiSocialPostsMediaCreate = <
|
||||
queryClient,
|
||||
);
|
||||
};
|
||||
/**
|
||||
* Saves the post for the current user, or unsaves it if already saved. Returns `{saved: true/false}`.
|
||||
* @summary Toggle save on a post
|
||||
*/
|
||||
export const apiSocialPostsSaveCreate = (
|
||||
id: number,
|
||||
post: NonReadonly<Post>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return privateMutator<ApiSocialPostsSaveCreate200>({
|
||||
url: `/api/social/posts/${id}/save/`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
data: post,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getApiSocialPostsSaveCreateMutationOptions = <
|
||||
TError = unknown,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof apiSocialPostsSaveCreate>>,
|
||||
TError,
|
||||
{ id: number; data: NonReadonly<Post> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof apiSocialPostsSaveCreate>>,
|
||||
TError,
|
||||
{ id: number; data: NonReadonly<Post> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["apiSocialPostsSaveCreate"];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof apiSocialPostsSaveCreate>>,
|
||||
{ id: number; data: NonReadonly<Post> }
|
||||
> = (props) => {
|
||||
const { id, data } = props ?? {};
|
||||
|
||||
return apiSocialPostsSaveCreate(id, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ApiSocialPostsSaveCreateMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof apiSocialPostsSaveCreate>>
|
||||
>;
|
||||
export type ApiSocialPostsSaveCreateMutationBody = NonReadonly<Post>;
|
||||
export type ApiSocialPostsSaveCreateMutationError = unknown;
|
||||
|
||||
/**
|
||||
* @summary Toggle save on a post
|
||||
*/
|
||||
export const useApiSocialPostsSaveCreate = <
|
||||
TError = unknown,
|
||||
TContext = unknown,
|
||||
>(
|
||||
options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof apiSocialPostsSaveCreate>>,
|
||||
TError,
|
||||
{ id: number; data: NonReadonly<Post> },
|
||||
TContext
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseMutationResult<
|
||||
Awaited<ReturnType<typeof apiSocialPostsSaveCreate>>,
|
||||
TError,
|
||||
{ id: number; data: NonReadonly<Post> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(
|
||||
getApiSocialPostsSaveCreateMutationOptions(options),
|
||||
queryClient,
|
||||
);
|
||||
};
|
||||
/**
|
||||
* Attaches an existing hub tag to the post. The tag must belong to the same hub as the post. Any authenticated hub member can attach tags.
|
||||
* @summary Attach a tag to a post
|
||||
@@ -1229,3 +1319,162 @@ export function useApiSocialPostsFeedList<
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List posts saved by the current user
|
||||
*/
|
||||
export const apiSocialPostsSavedList = (
|
||||
params?: ApiSocialPostsSavedListParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return privateMutator<PaginatedPostList>({
|
||||
url: `/api/social/posts/saved/`,
|
||||
method: "GET",
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getApiSocialPostsSavedListQueryKey = (
|
||||
params?: ApiSocialPostsSavedListParams,
|
||||
) => {
|
||||
return [`/api/social/posts/saved/`, ...(params ? [params] : [])] as const;
|
||||
};
|
||||
|
||||
export const getApiSocialPostsSavedListQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof apiSocialPostsSavedList>>,
|
||||
TError = unknown,
|
||||
>(
|
||||
params?: ApiSocialPostsSavedListParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof apiSocialPostsSavedList>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getApiSocialPostsSavedListQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof apiSocialPostsSavedList>>
|
||||
> = ({ signal }) => apiSocialPostsSavedList(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof apiSocialPostsSavedList>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
};
|
||||
|
||||
export type ApiSocialPostsSavedListQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof apiSocialPostsSavedList>>
|
||||
>;
|
||||
export type ApiSocialPostsSavedListQueryError = unknown;
|
||||
|
||||
export function useApiSocialPostsSavedList<
|
||||
TData = Awaited<ReturnType<typeof apiSocialPostsSavedList>>,
|
||||
TError = unknown,
|
||||
>(
|
||||
params: undefined | ApiSocialPostsSavedListParams,
|
||||
options: {
|
||||
query: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof apiSocialPostsSavedList>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
> &
|
||||
Pick<
|
||||
DefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof apiSocialPostsSavedList>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof apiSocialPostsSavedList>>
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): DefinedUseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useApiSocialPostsSavedList<
|
||||
TData = Awaited<ReturnType<typeof apiSocialPostsSavedList>>,
|
||||
TError = unknown,
|
||||
>(
|
||||
params?: ApiSocialPostsSavedListParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof apiSocialPostsSavedList>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
> &
|
||||
Pick<
|
||||
UndefinedInitialDataOptions<
|
||||
Awaited<ReturnType<typeof apiSocialPostsSavedList>>,
|
||||
TError,
|
||||
Awaited<ReturnType<typeof apiSocialPostsSavedList>>
|
||||
>,
|
||||
"initialData"
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
export function useApiSocialPostsSavedList<
|
||||
TData = Awaited<ReturnType<typeof apiSocialPostsSavedList>>,
|
||||
TError = unknown,
|
||||
>(
|
||||
params?: ApiSocialPostsSavedListParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof apiSocialPostsSavedList>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
};
|
||||
/**
|
||||
* @summary List posts saved by the current user
|
||||
*/
|
||||
|
||||
export function useApiSocialPostsSavedList<
|
||||
TData = Awaited<ReturnType<typeof apiSocialPostsSavedList>>,
|
||||
TError = unknown,
|
||||
>(
|
||||
params?: ApiSocialPostsSavedListParams,
|
||||
options?: {
|
||||
query?: Partial<
|
||||
UseQueryOptions<
|
||||
Awaited<ReturnType<typeof apiSocialPostsSavedList>>,
|
||||
TError,
|
||||
TData
|
||||
>
|
||||
>;
|
||||
},
|
||||
queryClient?: QueryClient,
|
||||
): UseQueryResult<TData, TError> & {
|
||||
queryKey: DataTag<QueryKey, TData, TError>;
|
||||
} {
|
||||
const queryOptions = getApiSocialPostsSavedListQueryOptions(params, options);
|
||||
|
||||
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||
TData,
|
||||
TError
|
||||
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
@@ -23,4 +23,6 @@ export interface PatchedPost {
|
||||
readonly vote_score?: string;
|
||||
readonly user_vote?: string;
|
||||
readonly reply_count?: number;
|
||||
readonly is_saved?: string;
|
||||
readonly save_count?: string;
|
||||
}
|
||||
|
||||
@@ -23,4 +23,6 @@ export interface Post {
|
||||
readonly vote_score: string;
|
||||
readonly user_vote: string;
|
||||
readonly reply_count: number;
|
||||
readonly is_saved: string;
|
||||
readonly save_count: string;
|
||||
}
|
||||
|
||||
@@ -1,31 +1,89 @@
|
||||
import axios, { type AxiosRequestConfig } from "axios";
|
||||
import { AUTH_FLAG } from "@/context/AuthContext";
|
||||
|
||||
// použij tohle pro API vyžadující autentizaci
|
||||
export const privateApi = axios.create({
|
||||
withCredentials: true, // potřebuje HttpOnly cookies
|
||||
withCredentials: true,
|
||||
baseURL: '',
|
||||
});
|
||||
|
||||
// Set baseURL at runtime (using Function to hide from orval's esbuild)
|
||||
try {
|
||||
const getEnv = new Function('return import.meta.env.VITE_BACKEND_URL');
|
||||
privateApi.defaults.baseURL = getEnv() || "http://localhost:8000";
|
||||
} catch {
|
||||
privateApi.defaults.baseURL = "http://localhost:8000";
|
||||
}
|
||||
let isRefreshing = false;
|
||||
let failedQueue: Array<{
|
||||
resolve: (value?: unknown) => void;
|
||||
reject: (reason?: any) => void;
|
||||
}> = [];
|
||||
|
||||
const processQueue = (error: any = null) => {
|
||||
failedQueue.forEach((promise) => {
|
||||
if (error) {
|
||||
promise.reject(error);
|
||||
} else {
|
||||
promise.resolve();
|
||||
}
|
||||
});
|
||||
failedQueue = [];
|
||||
};
|
||||
|
||||
privateApi.interceptors.response.use(
|
||||
(res) => res,
|
||||
async (error) => {
|
||||
const original = error.config;
|
||||
|
||||
if (error.response?.status === 400 && error.response?.data) {
|
||||
const data = error.response.data;
|
||||
if (typeof data === 'object' && !Array.isArray(data)) {
|
||||
const firstKey = Object.keys(data)[0];
|
||||
if (firstKey && Array.isArray(data[firstKey]) && data[firstKey].length > 0) {
|
||||
error.message = data[firstKey][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (error.response?.status === 401 && !original._retry) {
|
||||
if (original.url?.includes("/api/account/logout/")) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (error.response?.data?.code === "user_not_found") {
|
||||
processQueue(error);
|
||||
isRefreshing = false;
|
||||
localStorage.removeItem(AUTH_FLAG);
|
||||
window.location.href = "/social/login";
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (original.url?.includes("/api/account/token/refresh/")) {
|
||||
processQueue(error);
|
||||
isRefreshing = false;
|
||||
localStorage.removeItem(AUTH_FLAG);
|
||||
try {
|
||||
await privateApi.post("/api/account/logout/");
|
||||
} catch {
|
||||
// cookies may already be expired
|
||||
}
|
||||
window.location.href = "/social/login";
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (isRefreshing) {
|
||||
return new Promise((resolve, reject) => {
|
||||
failedQueue.push({ resolve, reject });
|
||||
})
|
||||
.then(() => privateApi(original))
|
||||
.catch((err) => Promise.reject(err));
|
||||
}
|
||||
|
||||
original._retry = true;
|
||||
isRefreshing = true;
|
||||
|
||||
try {
|
||||
await privateApi.post("/api/account/token/refresh/");
|
||||
processQueue();
|
||||
return privateApi(original);
|
||||
} catch {
|
||||
// optional: logout
|
||||
} catch (refreshError) {
|
||||
processQueue(refreshError);
|
||||
return Promise.reject(refreshError);
|
||||
} finally {
|
||||
isRefreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,15 +91,10 @@ privateApi.interceptors.response.use(
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
export const privateMutator = async <T>(
|
||||
config: AxiosRequestConfig
|
||||
): Promise<T> => {
|
||||
// If sending FormData, remove Content-Type header to let axios set it with boundary
|
||||
export const privateMutator = async <T>(config: AxiosRequestConfig): Promise<T> => {
|
||||
if (config.data instanceof FormData) {
|
||||
delete config.headers?.['Content-Type'];
|
||||
}
|
||||
|
||||
const response = await privateApi.request<T>(config);
|
||||
return response.data;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,23 +1,46 @@
|
||||
import axios, { type AxiosRequestConfig } from "axios";
|
||||
import { AUTH_FLAG } from "@/context/AuthContext";
|
||||
|
||||
// použij tohle pro veřejné API nevyžadující autentizaci
|
||||
export const publicApi = axios.create({
|
||||
withCredentials: false, // veřejné API NEPOSÍLÁ cookies
|
||||
withCredentials: true,
|
||||
baseURL: '',
|
||||
});
|
||||
|
||||
// Set baseURL at runtime (using Function to hide from orval's esbuild)
|
||||
try {
|
||||
const getEnv = new Function('return import.meta.env.VITE_BACKEND_URL');
|
||||
publicApi.defaults.baseURL = getEnv() || "http://localhost:8000";
|
||||
} catch {
|
||||
publicApi.defaults.baseURL = "http://localhost:8000";
|
||||
}
|
||||
publicApi.interceptors.response.use(
|
||||
(res) => res,
|
||||
(error) => {
|
||||
const url = error.config?.url ?? '';
|
||||
if (
|
||||
error.response?.status === 401 &&
|
||||
error.response?.data?.code === "user_not_found" &&
|
||||
!url.includes("/api/account/logout/")
|
||||
) {
|
||||
localStorage.removeItem(AUTH_FLAG);
|
||||
window.location.href = "/social/login";
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
const pendingRequests = new Map();
|
||||
|
||||
// ⬇⬇⬇ TOHLE JE TEN MUTATOR ⬇⬇⬇
|
||||
export const publicMutator = async <T>(
|
||||
config: AxiosRequestConfig
|
||||
): Promise<T> => {
|
||||
const response = await publicApi.request<T>(config);
|
||||
return response.data;
|
||||
};
|
||||
export const publicMutator = async <T>(config: AxiosRequestConfig): Promise<T> => {
|
||||
const requestKey = `${config.method}_${config.url}_${JSON.stringify(config.data || '')}`;
|
||||
|
||||
if (pendingRequests.has(requestKey)) {
|
||||
return pendingRequests.get(requestKey);
|
||||
}
|
||||
|
||||
const requestPromise = (async () => {
|
||||
try {
|
||||
const response = await publicApi.request<T>(config);
|
||||
return response.data;
|
||||
} finally {
|
||||
pendingRequests.delete(requestKey);
|
||||
}
|
||||
})();
|
||||
|
||||
pendingRequests.set(requestKey, requestPromise);
|
||||
|
||||
return requestPromise;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user