Fix project-route auth gaps for domain and SERP lookups (#65)
This commit is contained in:
parent
5d093c1ebb
commit
b15b62cae6
@ -225,14 +225,20 @@ export function useSyncRouteState({
|
|||||||
}, [navigate]);
|
}, [navigate]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDomainLookupMutation() {
|
export function useDomainLookupMutation(projectId: string) {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (data: {
|
mutationFn: (data: {
|
||||||
domain: string;
|
domain: string;
|
||||||
includeSubdomains: boolean;
|
includeSubdomains: boolean;
|
||||||
locationCode: number;
|
locationCode: number;
|
||||||
languageCode: string;
|
languageCode: string;
|
||||||
}) => getDomainOverview({ data }),
|
}) =>
|
||||||
|
getDomainOverview({
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
projectId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -160,7 +160,7 @@ export function useDomainOverviewController({
|
|||||||
});
|
});
|
||||||
|
|
||||||
useSyncRouteState({ controlsForm, searchState, setPendingSearch, navigate });
|
useSyncRouteState({ controlsForm, searchState, setPendingSearch, navigate });
|
||||||
const domainMutation = useDomainLookupMutation();
|
const domainMutation = useDomainLookupMutation(projectId);
|
||||||
const saveMutation = useSaveKeywordsMutation({ projectId, queryClient });
|
const saveMutation = useSaveKeywordsMutation({ projectId, queryClient });
|
||||||
const dataState = useOverviewDataState({
|
const dataState = useOverviewDataState({
|
||||||
overview,
|
overview,
|
||||||
|
|||||||
@ -4,16 +4,20 @@ import { getStandardErrorMessage } from "@/client/lib/error-messages";
|
|||||||
import { getLanguageCode } from "@/client/features/keywords/utils";
|
import { getLanguageCode } from "@/client/features/keywords/utils";
|
||||||
import { getSerpAnalysis } from "@/serverFunctions/keywords";
|
import { getSerpAnalysis } from "@/serverFunctions/keywords";
|
||||||
|
|
||||||
export function useKeywordSerpAnalysis(locationCode: number) {
|
export function useKeywordSerpAnalysis(
|
||||||
|
projectId: string,
|
||||||
|
locationCode: number,
|
||||||
|
) {
|
||||||
const [serpKeyword, setSerpKeyword] = useState<string | null>(null);
|
const [serpKeyword, setSerpKeyword] = useState<string | null>(null);
|
||||||
const [serpPage, setSerpPage] = useState(0);
|
const [serpPage, setSerpPage] = useState(0);
|
||||||
const SERP_PAGE_SIZE = 10;
|
const SERP_PAGE_SIZE = 10;
|
||||||
|
|
||||||
const serpQuery = useQuery({
|
const serpQuery = useQuery({
|
||||||
queryKey: ["serpAnalysis", serpKeyword, locationCode],
|
queryKey: ["serpAnalysis", projectId, serpKeyword, locationCode],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
getSerpAnalysis({
|
getSerpAnalysis({
|
||||||
data: {
|
data: {
|
||||||
|
projectId,
|
||||||
keyword: serpKeyword!,
|
keyword: serpKeyword!,
|
||||||
locationCode,
|
locationCode,
|
||||||
languageCode: getLanguageCode(locationCode),
|
languageCode: getLanguageCode(locationCode),
|
||||||
|
|||||||
@ -165,7 +165,7 @@ function useKeywordControllerState(input: KeywordResearchControllerInput) {
|
|||||||
activeSerpKeyword,
|
activeSerpKeyword,
|
||||||
serpLoading,
|
serpLoading,
|
||||||
serpError,
|
serpError,
|
||||||
} = useKeywordSerpAnalysis(locationCode);
|
} = useKeywordSerpAnalysis(input.projectId, locationCode);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
history,
|
history,
|
||||||
|
|||||||
@ -69,6 +69,7 @@ const domainOverviewSchema = z.object({
|
|||||||
|
|
||||||
async function getOverview(
|
async function getOverview(
|
||||||
input: {
|
input: {
|
||||||
|
projectId: string;
|
||||||
domain: string;
|
domain: string;
|
||||||
includeSubdomains: boolean;
|
includeSubdomains: boolean;
|
||||||
locationCode: number;
|
locationCode: number;
|
||||||
@ -80,6 +81,7 @@ async function getOverview(
|
|||||||
|
|
||||||
const cacheKey = await buildCacheKey("domain:overview", {
|
const cacheKey = await buildCacheKey("domain:overview", {
|
||||||
organizationId: billingCustomer.organizationId,
|
organizationId: billingCustomer.organizationId,
|
||||||
|
projectId: input.projectId,
|
||||||
domain,
|
domain,
|
||||||
includeSubdomains: input.includeSubdomains,
|
includeSubdomains: input.includeSubdomains,
|
||||||
locationCode: input.locationCode,
|
locationCode: input.locationCode,
|
||||||
|
|||||||
@ -56,6 +56,7 @@ function mapOrganicSerpItems(items: SerpLiveItem[]): SerpResultItem[] {
|
|||||||
|
|
||||||
async function getSerpLiveAnalysis(
|
async function getSerpLiveAnalysis(
|
||||||
input: {
|
input: {
|
||||||
|
projectId: string;
|
||||||
keyword: string;
|
keyword: string;
|
||||||
locationCode: number;
|
locationCode: number;
|
||||||
languageCode: string;
|
languageCode: string;
|
||||||
@ -66,6 +67,7 @@ async function getSerpLiveAnalysis(
|
|||||||
|
|
||||||
const cacheKey = await buildCacheKey("serp:analysis", {
|
const cacheKey = await buildCacheKey("serp:analysis", {
|
||||||
organizationId: billingCustomer.organizationId,
|
organizationId: billingCustomer.organizationId,
|
||||||
|
projectId: input.projectId,
|
||||||
keyword,
|
keyword,
|
||||||
locationCode: input.locationCode,
|
locationCode: input.locationCode,
|
||||||
languageCode: input.languageCode,
|
languageCode: input.languageCode,
|
||||||
|
|||||||
@ -1,14 +1,20 @@
|
|||||||
import { createServerFn } from "@tanstack/react-start";
|
import { createServerFn } from "@tanstack/react-start";
|
||||||
import { requireAuthenticatedContext } from "@/serverFunctions/middleware";
|
import { requireProjectContext } from "@/serverFunctions/middleware";
|
||||||
import { domainOverviewSchema } from "@/types/schemas/domain";
|
import { domainOverviewSchema } from "@/types/schemas/domain";
|
||||||
import { DomainService } from "@/server/features/domain/services/DomainService";
|
import { DomainService } from "@/server/features/domain/services/DomainService";
|
||||||
|
|
||||||
export const getDomainOverview = createServerFn({ method: "POST" })
|
export const getDomainOverview = createServerFn({ method: "POST" })
|
||||||
.middleware(requireAuthenticatedContext)
|
.middleware(requireProjectContext)
|
||||||
.inputValidator((data: unknown) => domainOverviewSchema.parse(data))
|
.inputValidator((data: unknown) => domainOverviewSchema.parse(data))
|
||||||
.handler(async ({ data, context }) =>
|
.handler(async ({ data, context }) =>
|
||||||
DomainService.getOverview(data, {
|
DomainService.getOverview(
|
||||||
|
{
|
||||||
|
...data,
|
||||||
|
projectId: context.project.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
organizationId: context.organizationId,
|
organizationId: context.organizationId,
|
||||||
userEmail: context.userEmail,
|
userEmail: context.userEmail,
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -7,10 +7,7 @@ import {
|
|||||||
serpAnalysisSchema,
|
serpAnalysisSchema,
|
||||||
} from "@/types/schemas/keywords";
|
} from "@/types/schemas/keywords";
|
||||||
import { KeywordResearchService } from "@/server/features/keywords/services/KeywordResearchService";
|
import { KeywordResearchService } from "@/server/features/keywords/services/KeywordResearchService";
|
||||||
import {
|
import { requireProjectContext } from "@/serverFunctions/middleware";
|
||||||
requireAuthenticatedContext,
|
|
||||||
requireProjectContext,
|
|
||||||
} from "@/serverFunctions/middleware";
|
|
||||||
|
|
||||||
export const researchKeywords = createServerFn({ method: "POST" })
|
export const researchKeywords = createServerFn({ method: "POST" })
|
||||||
.middleware(requireProjectContext)
|
.middleware(requireProjectContext)
|
||||||
@ -58,11 +55,17 @@ export const removeSavedKeyword = createServerFn({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getSerpAnalysis = createServerFn({ method: "POST" })
|
export const getSerpAnalysis = createServerFn({ method: "POST" })
|
||||||
.middleware(requireAuthenticatedContext)
|
.middleware(requireProjectContext)
|
||||||
.inputValidator((data: unknown) => serpAnalysisSchema.parse(data))
|
.inputValidator((data: unknown) => serpAnalysisSchema.parse(data))
|
||||||
.handler(async ({ data, context }) =>
|
.handler(async ({ data, context }) =>
|
||||||
KeywordResearchService.getSerpAnalysis(data, {
|
KeywordResearchService.getSerpAnalysis(
|
||||||
|
{
|
||||||
|
...data,
|
||||||
|
projectId: context.project.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
organizationId: context.organizationId,
|
organizationId: context.organizationId,
|
||||||
userEmail: context.userEmail,
|
userEmail: context.userEmail,
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,6 +5,7 @@ const booleanSearchParamSchema = z
|
|||||||
.transform((value) => value === true || value === "true");
|
.transform((value) => value === true || value === "true");
|
||||||
|
|
||||||
export const domainOverviewSchema = z.object({
|
export const domainOverviewSchema = z.object({
|
||||||
|
projectId: z.string().min(1),
|
||||||
domain: z.string().min(1, "Domain is required").max(255),
|
domain: z.string().min(1, "Domain is required").max(255),
|
||||||
includeSubdomains: z.boolean().default(true),
|
includeSubdomains: z.boolean().default(true),
|
||||||
locationCode: z.number().int().positive().default(2840),
|
locationCode: z.number().int().positive().default(2840),
|
||||||
|
|||||||
@ -71,6 +71,7 @@ export type ResearchKeywordsInput = z.infer<typeof researchKeywordsSchema>;
|
|||||||
export type SaveKeywordsInput = z.infer<typeof saveKeywordsSchema>;
|
export type SaveKeywordsInput = z.infer<typeof saveKeywordsSchema>;
|
||||||
export type RemoveSavedKeywordInput = z.infer<typeof removeSavedKeywordSchema>;
|
export type RemoveSavedKeywordInput = z.infer<typeof removeSavedKeywordSchema>;
|
||||||
export const serpAnalysisSchema = z.object({
|
export const serpAnalysisSchema = z.object({
|
||||||
|
projectId: z.string().min(1),
|
||||||
keyword: z.string().min(1),
|
keyword: z.string().min(1),
|
||||||
locationCode: z.number().int().positive().default(2840),
|
locationCode: z.number().int().positive().default(2840),
|
||||||
languageCode: z.string().min(2).max(8).default("en"),
|
languageCode: z.string().min(2).max(8).default("en"),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user