Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | 2x 2x 16x 16x 16x 16x 16x 16x 2x 14x 14x 2x 12x 12x 2x 10x 10x 10x 10x 10x 4x 10x 10x 10x 7x 10x 6x 2x 11x 11x 11x 11x 11x 2x 9x 9x 2x 7x 7x 7x 7x 7x 5x 7x 7x 7x 7x 7x 4x 2x 2x 9x 9x 9x 9x 1x 8x 8x 4x 4x 2x 2x 1x | // Internal Pro Data Routes (for build worker and SSR preview)
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
import type { Dal } from "../../../dal";
import type { Services } from "../../../services";
import { success, handleError } from "../../../lib/response";
import { NotFoundError } from "../../../lib/errors";
import { validatePreviewToken } from "../../../lib/preview-token";
import { logger } from "../../../lib/logger";
import {
buildAggregatedCompanyProfile,
fetchProjectsWithDetails,
fetchProBlogs,
} from "./build-helpers";
type Env = {
Bindings: CloudflareBindings;
Variables: {
dal: Dal;
services: Services;
};
};
const buildProDataRoutes = new Hono<Env>();
// Get pro data for building site
buildProDataRoutes.get("/:jobId/pro-data", async (c) => {
try {
const dal = c.get("dal");
const services = c.get("services");
const jobId = parseInt(c.req.param("jobId"), 10);
const job = await dal.websiteBuildJobs.findBuildJobById(jobId);
if (!job) {
throw new NotFoundError("Build job not found");
}
const website = await dal.proWebsites.findById(job.proWebsiteId);
if (!website) {
throw new NotFoundError("Website not found");
}
// Get full pro data
const pro = await services.pro.getById(website.proId);
if (!pro) {
throw new NotFoundError("Pro not found");
}
// Get template
const template = await dal.websiteTemplates.findById(website.templateId);
// Get all published projects with photos, rooms, and media
const projects = await fetchProjectsWithDetails(dal, pro.id);
// Get aggregated company profile (with leadership, certifications, testimonials)
// Non-fatal: a build should not fail due to missing/errored company profile
let companyProfile = null;
try {
companyProfile = await buildAggregatedCompanyProfile(dal, pro.id);
} catch (err) {
logger.error("[build-pro-data] Failed to fetch company profile for pro", pro.id, ":", err);
}
// Get published blogs for this pro
// Non-fatal: a build should not fail due to missing/errored blogs
let blogs: Awaited<ReturnType<typeof fetchProBlogs>> = [];
try {
blogs = await fetchProBlogs(dal, pro.id);
} catch (err) {
logger.error("[build-pro-data] Failed to fetch blogs for pro", pro.id, ":", err);
}
return success(c, {
website,
template,
pro,
projects,
companyProfile,
blogs,
});
} catch (err) {
return handleError(c, err);
}
});
// Get pro data by slug (for SSR preview)
buildProDataRoutes.get("/preview/:slug", async (c) => {
try {
const dal = c.get("dal");
const slug = c.req.param("slug");
// Find pro by slug
const pro = await dal.pros.findBySlug(slug);
if (!pro) {
throw new NotFoundError("Pro not found for slug");
}
// Find website by proId
const website = await dal.proWebsites.findByProId(pro.id);
if (!website) {
throw new NotFoundError("Website not found for pro");
}
// Get template
const template = await dal.websiteTemplates.findById(website.templateId);
// Get all published projects with photos, rooms, and media
const projects = await fetchProjectsWithDetails(dal, pro.id);
// Get aggregated company profile (with leadership, certifications, testimonials)
// Non-fatal: a build should not fail due to missing/errored company profile
let companyProfile = null;
try {
companyProfile = await buildAggregatedCompanyProfile(dal, pro.id);
} catch (err) {
logger.error("[build-pro-data] Failed to fetch company profile for pro", pro.id, ":", err);
}
// Get published blogs for this pro
// Non-fatal: a build should not fail due to missing/errored blogs
let blogs: Awaited<ReturnType<typeof fetchProBlogs>> = [];
try {
blogs = await fetchProBlogs(dal, pro.id);
} catch (err) {
logger.error("[build-pro-data] Failed to fetch blogs for pro", pro.id, ":", err);
}
return success(c, {
website,
template,
pro,
projects,
companyProfile,
blogs,
});
} catch (err) {
return handleError(c, err);
}
});
// Validate preview token (for pro-sites SSR preview)
// This endpoint does NOT require the internal API key - it's called by the public-facing preview
const validateTokenSchema = z.object({
token: z.string().min(1),
slug: z.string().min(1),
});
buildProDataRoutes.post(
"/validate-preview-token",
zValidator("json", validateTokenSchema),
async (c) => {
try {
const { token, slug } = c.req.valid("json");
// Get the secret used for signing tokens
// Reuses BETTER_AUTH_SECRET for validation (same as used for signing)
const secret = c.env.BETTER_AUTH_SECRET;
if (!secret) {
throw new Error(
"BETTER_AUTH_SECRET not configured for token validation",
);
}
// Validate the token
const payload = await validatePreviewToken(token, secret);
if (!payload) {
return success(c, { valid: false, reason: "Invalid or expired token" });
}
// Check if the token matches the requested slug
if (payload.sub !== slug) {
return success(c, {
valid: false,
reason: "Token slug mismatch",
});
}
return success(c, {
valid: true,
slug: payload.sub,
proId: payload.vid,
userId: payload.uid,
expiresAt: new Date(payload.exp * 1000).toISOString(),
});
} catch (err) {
return handleError(c, err);
}
},
);
export default buildProDataRoutes;
|