Ben Senescu 35700ca80a
Improve error handling + local setup (#4)
* 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
2026-03-04 16:27:41 -05:00

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),
);