* Add unified Docker self-host stack for OpenSEO and Gateway * Fix self-host migration flow and env wiring * Track latest gateway release by default and harden migrations * Fix gateway static asset serving in self-host runtime * Run gateway self-host from source with .env-based Vite runtime * Harden self-host env mode and remove apt dependency from gateway build * Avoid gateway build ENOSPC by installing deps at runtime * Revert temporary ENOSPC workaround for gateway Docker setup * Use Cloudflare selfhost env precedence for Docker runtime * Add selfhost wrangler environment and tighten Docker docs * Stabilize selfhost Docker runtime and persistence Use full Node base images for reliable TLS trust, simplify selfhost startup/env handling, and persist local gateway wrangler state to avoid resets between restarts. * Simplify Docker selfhost: rename scripts to docker:selfhost:*, remove GATEWAY_RELEASE_REPO, default to latest gateway * Remove selfhost wrangler env, use .env.local everywhere, simplify docker script names * Simplify selfhost compose: remove D1 preflight, conditional migration, and healthcheck * Extract inline Node.js from gateway Dockerfile into standalone scripts * Save * Move OpenSEO dependency install from container startup to image build step * Move self-host docker scripts and compose into dedicated folder * save * disable TanStack devtools in Docker self-host * Make edits * clarify self-hosting env setup docs * save * save * centralize server-function error middleware and simplify client-safe messaging * remove local migration-specific error remapping Keep auth middleware simple by letting DB/schema failures flow through the centralized error handler and remove the unused LOCAL_DB_MIGRATION_REQUIRED client/server code path. * remove custom error logging in favor of plain console.error * standardize PSI error codes and sanitize non-Error throws
84 lines
3.3 KiB
TypeScript
84 lines
3.3 KiB
TypeScript
import { createServerFn } from "@tanstack/react-start";
|
|
import { z } from "zod";
|
|
import { authenticatedServerFunctionMiddleware } from "@/serverFunctions/middleware";
|
|
import {
|
|
researchKeywordsSchema,
|
|
createProjectSchema,
|
|
deleteProjectSchema,
|
|
saveKeywordsSchema,
|
|
getSavedKeywordsSchema,
|
|
removeSavedKeywordSchema,
|
|
serpAnalysisSchema,
|
|
} from "@/types/schemas/keywords";
|
|
import { KeywordResearchService } from "@/server/services/KeywordResearchService";
|
|
|
|
export const researchKeywords = createServerFn({ method: "POST" })
|
|
.middleware(authenticatedServerFunctionMiddleware)
|
|
.inputValidator((data: unknown) => researchKeywordsSchema.parse(data))
|
|
.handler(async ({ data, context }) =>
|
|
KeywordResearchService.research(context.userId, data),
|
|
);
|
|
|
|
export const listProjects = createServerFn({ method: "POST" })
|
|
.middleware(authenticatedServerFunctionMiddleware)
|
|
.handler(async ({ context }) =>
|
|
KeywordResearchService.listProjects(context.userId),
|
|
);
|
|
|
|
export const createProject = createServerFn({ method: "POST" })
|
|
.middleware(authenticatedServerFunctionMiddleware)
|
|
.inputValidator((data: unknown) => createProjectSchema.parse(data))
|
|
.handler(async ({ data, context }) =>
|
|
KeywordResearchService.createProject(context.userId, data),
|
|
);
|
|
|
|
export const deleteProject = createServerFn({ method: "POST" })
|
|
.middleware(authenticatedServerFunctionMiddleware)
|
|
.inputValidator((data: unknown) => deleteProjectSchema.parse(data))
|
|
.handler(async ({ data, context }) =>
|
|
KeywordResearchService.deleteProject(context.userId, data),
|
|
);
|
|
|
|
export const saveKeywords = createServerFn({ method: "POST" })
|
|
.middleware(authenticatedServerFunctionMiddleware)
|
|
.inputValidator((data: unknown) => saveKeywordsSchema.parse(data))
|
|
.handler(async ({ data, context }) =>
|
|
KeywordResearchService.saveKeywords(context.userId, data),
|
|
);
|
|
|
|
export const getSavedKeywords = createServerFn({ method: "POST" })
|
|
.middleware(authenticatedServerFunctionMiddleware)
|
|
.inputValidator((data: unknown) => getSavedKeywordsSchema.parse(data))
|
|
.handler(async ({ data, context }) =>
|
|
KeywordResearchService.getSavedKeywords(context.userId, data),
|
|
);
|
|
|
|
export const removeSavedKeyword = createServerFn({ method: "POST" })
|
|
.middleware(authenticatedServerFunctionMiddleware)
|
|
.inputValidator((data: unknown) => removeSavedKeywordSchema.parse(data))
|
|
.handler(async ({ data, context }) =>
|
|
KeywordResearchService.removeSavedKeyword(context.userId, data),
|
|
);
|
|
|
|
export const getOrCreateDefaultProject = createServerFn({ method: "POST" })
|
|
.middleware(authenticatedServerFunctionMiddleware)
|
|
.handler(async ({ context }) =>
|
|
KeywordResearchService.getOrCreateDefaultProject(context.userId),
|
|
);
|
|
|
|
export const getSerpAnalysis = createServerFn({ method: "POST" })
|
|
.middleware(authenticatedServerFunctionMiddleware)
|
|
.inputValidator((data: unknown) => serpAnalysisSchema.parse(data))
|
|
.handler(async ({ data }) => KeywordResearchService.getSerpAnalysis(data));
|
|
|
|
const getProjectSchema = z.object({
|
|
projectId: z.string().min(1),
|
|
});
|
|
|
|
export const getProject = createServerFn({ method: "POST" })
|
|
.middleware(authenticatedServerFunctionMiddleware)
|
|
.inputValidator((data: unknown) => getProjectSchema.parse(data))
|
|
.handler(async ({ data, context }) =>
|
|
KeywordResearchService.getProject(context.userId, data.projectId),
|
|
);
|