chore(console): remove legacy Console leftovers from the website (#57434)

This commit is contained in:
Victor Navarro 2026-10-20 19:05:13 +02:00 • committed by GitHub
parent 5916014733
commit 772e2aed12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 19 additions and 569 deletions

View file

@ -1,20 +1,6 @@
import { domain } from "./stage"
import { EMAILOCTOPUS_API_KEY } from "./app"
// Preview stages have no new Console or inference deployment to forward to.
const migrationDomain =
$app.stage === "production" ? "opencode.ai" : $app.stage === "dev" ? "dev.opencode.ai" : undefined
const consoleMigration = new sst.Linkable("ConsoleMigration", {
properties: {
consoleUrl: migrationDomain ? `https://${migrationDomain}/console` : "",
inferenceUrl: migrationDomain ? `https://${migrationDomain}/inference` : "",
},
})
////////////////
// CONSOLE
////////////////
// Archived Zen request/response dumps (writer removed May 2026). Retained on purpose; nothing reads or writes them.
new sst.cloudflare.Bucket("ZenData")
new sst.cloudflare.Bucket("ZenDataNew")
@ -29,16 +15,11 @@ const HCAPTCHA_SECRET_KEY = new sst.Secret("HCAPTCHA_SECRET_KEY")
const SALESFORCE_CLIENT_ID = new sst.Secret("SALESFORCE_CLIENT_ID")
const SALESFORCE_CLIENT_SECRET = new sst.Secret("SALESFORCE_CLIENT_SECRET")
const SALESFORCE_INSTANCE_URL = new sst.Secret("SALESFORCE_INSTANCE_URL")
const CONSOLE_CRM_TOKEN = new sst.Secret("CONSOLE_CRM_TOKEN")
const CONSOLE_CRM_LOOKUP_URL = new sst.Linkable("CONSOLE_CRM_LOOKUP_URL", {
properties: { value: `https://${domain}/console/api/internal/console-account` },
})
new sst.cloudflare.x.SolidStart("Console", {
domain,
path: "packages/console/app",
link: [
consoleMigration,
EMAILOCTOPUS_API_KEY,
AWS_SES_ACCESS_KEY_ID,
AWS_SES_SECRET_ACCESS_KEY,
@ -47,8 +28,6 @@ new sst.cloudflare.x.SolidStart("Console", {
SALESFORCE_CLIENT_ID,
SALESFORCE_CLIENT_SECRET,
SALESFORCE_INSTANCE_URL,
CONSOLE_CRM_TOKEN,
CONSOLE_CRM_LOOKUP_URL,
],
environment: {
//VITE_DOCS_URL: web.url.apply((url) => url!),
@ -65,12 +44,6 @@ new sst.cloudflare.x.SolidStart("Console", {
...(flags ?? []),
"global_fetch_strictly_public",
])
// Same-zone fetch to inference re-enters as a public request and loses the client IP.
if (migrationDomain)
args.bindings = $resolve(args.bindings).apply((bindings) => [
...(bindings ?? []),
{ type: "service", name: "INFERENCE", service: `opencode-inference-${$app.stage}` },
])
},
},
},

View file

@ -1,19 +1,10 @@
import { handleEnterpriseRequest } from "../src/lib/enterprise"
import { z } from "zod"
import { SalesforceCliError, salesforceCliResponse } from "./salesforce-cli-response"
import { handleConsoleSignup } from "../src/lib/console-signup"
import { consoleAccountSchema } from "../src/lib/console-account"
const org = process.env.SF_TARGET_ORG || "opencode"
const crmToken = process.env.CONSOLE_CRM_TOKEN
if (!crmToken) throw new Error("Set CONSOLE_CRM_TOKEN to the shared local Console CRM token")
const accountUrl = process.env.CONSOLE_CRM_LOOKUP_URL || "http://localhost:3100/console/api/internal/console-account"
async function authenticatedRequest(
path: string,
payload?: Record<string, unknown>,
method: "GET" | "POST" | "PATCH" = payload ? "POST" : "GET",
) {
async function authenticatedRequest(path: string, payload?: Record<string, unknown>) {
const child = Bun.spawn(
[
"sf",
@ -25,7 +16,7 @@ async function authenticatedRequest(
org,
"--json",
"--method",
method,
payload ? "POST" : "GET",
...(payload ? ["--body", "-", "--header", "Content-Type:application/json"] : []),
],
{
@ -52,11 +43,6 @@ const server = Bun.serve({
if (path === "/health" && request.method === "GET") {
return Response.json({ handler: "local-sst-enterprise", salesforceOrg: org })
}
if (path === "/api/console-signup" && request.method === "POST") {
return handleConsoleSignup(request, crmToken, (path, method, payload) =>
authenticatedRequest(path, payload, method),
)
}
if (path !== "/api/enterprise") return new Response("Not found", { status: 404 })
if (request.method !== "POST") return new Response("Method not allowed", { status: 405 })
const failures: string[] = []
@ -73,16 +59,6 @@ const server = Bun.serve({
return z.object({ success: z.literal(true) }).safeParse(result).success
},
notifications: false,
accountLookup: async (email) => {
const response = await fetch(accountUrl, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${crmToken}` },
body: JSON.stringify({ email }),
signal: AbortSignal.timeout(8000),
})
if (!response.ok) throw new Error(`Local Console account lookup failed (${response.status})`)
return z.object({ account: consoleAccountSchema.nullable() }).parse(await response.json()).account
},
})
if (failures.length > 0) {
return Response.json(

View file

@ -1,31 +0,0 @@
import { Resource } from "@opencode/console-resource"
import { z } from "zod"
export const consoleAccountSchema = z.object({
id: z.string().min(1).max(255),
email: z.email(),
name: z.string().nullable(),
createdAt: z.iso.datetime(),
})
export type ConsoleAccount = z.infer<typeof consoleAccountSchema>
export async function lookupConsoleAccount(email: string): Promise<ConsoleAccount | null> {
const response = await fetch(`${Resource.CONSOLE_CRM_LOOKUP_URL.value}`, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${Resource.CONSOLE_CRM_TOKEN.value}` },
body: JSON.stringify({ email }),
signal: AbortSignal.timeout(3000),
})
if (!response.ok) throw new Error(`Console account lookup failed (${response.status})`)
const result = z.object({ account: consoleAccountSchema.nullable() }).parse(await response.json())
return result.account
}
export function consoleAccountFields(account: ConsoleAccount | null) {
if (!account) return { Has_Console_Account__c: false }
return {
Has_Console_Account__c: true,
Console_Account_ID__c: account.id,
Console_Signup_Date__c: account.createdAt,
}
}

View file

@ -1,159 +0,0 @@
import { afterEach, expect, test } from "bun:test"
import { syncConsoleAccount, type SalesforceRequest } from "./salesforce"
import { handleConsoleSignup } from "./console-signup"
import { z } from "zod"
const account = {
id: "user_console_test",
email: "person@example.test",
name: "Test Person",
createdAt: "2026-10-01T10:00:00.000Z",
}
const servers = new Set<ReturnType<typeof Bun.serve>>()
afterEach(async () => {
await Promise.all([...servers].map((server) => server.stop(true)))
servers.clear()
})
function fixture(
initial: { object: "Lead" | "Contact"; Id: string; LeadSource: string; Company?: string; AccountId?: string }[] = [],
email = account.email,
) {
const people: Record<string, unknown>[] = initial.map((person) => ({ ...person }))
const writes: { method: string; path: string; payload: Record<string, unknown> }[] = []
const server = Bun.serve({
hostname: "127.0.0.1",
port: 0,
async fetch(request) {
const url = new URL(request.url)
if (url.pathname.endsWith("/query")) {
const query = url.searchParams.get("q") ?? ""
expect(query).toContain("Console_Account_ID__c")
expect(query).toContain(`Email = '${email.toLowerCase()}'`)
const object = query.includes("FROM Contact") ? "Contact" : "Lead"
if (object === "Lead") expect(query).toContain("IsConverted = false")
return Response.json({
done: true,
records: people.filter((person) => person.object === object).map((person) => ({ Id: person.Id })),
})
}
const payload = z.record(z.string(), z.unknown()).parse(await request.json())
writes.push({ method: request.method, path: url.pathname, payload })
if (request.method === "PATCH") {
const person = people.find((person) => person.Id === url.pathname.split("/").at(-1))
if (!person) return Response.json({ error: "Not found" }, { status: 404 })
Object.assign(person, payload)
return new Response(null, { status: 204 })
}
const id = "00Q000000000001AAA"
people.push({ object: "Lead", Id: id, ...payload })
return Response.json({ success: true, id }, { status: 201 })
},
})
servers.add(server)
const request: SalesforceRequest = async (path, method = "GET", payload) => {
const response = await fetch(new URL(path, server.url), {
method,
...(payload ? { body: JSON.stringify(payload), headers: { "Content-Type": "application/json" } } : {}),
})
if (!response.ok) throw new Error("CRM request failed")
return response.status === 204 ? null : response.json()
}
return { people, writes, request }
}
test("a new signup creates one Console lead and an exact retry updates it", async () => {
const crm = fixture()
await syncConsoleAccount(account, crm.request)
await syncConsoleAccount(account, crm.request)
expect(crm.people).toHaveLength(1)
expect(crm.writes.filter((write) => write.method === "POST")).toHaveLength(1)
expect(crm.people[0]).toMatchObject({
Company: "example.test",
LeadSource: "console signup",
Has_Console_Account__c: true,
Console_Account_ID__c: account.id,
Console_Signup_Date__c: account.createdAt,
})
expect(crm.writes[1].payload).not.toHaveProperty("LeadSource")
expect(crm.writes[1].payload).not.toHaveProperty("Company")
})
test.each([
["hello@acme.com", "acme.com"],
["jane@company.co.uk", "company.co.uk"],
["HELLO@ACME.COM", "acme.com"],
["person@gmail.com", "Individual"],
["person+signup@GOOGLEMAIL.COM", "Individual"],
["person@outlook.com", "Individual"],
["person@hotmail.co.uk", "Individual"],
["person@yahoo.com", "Individual"],
["person@icloud.com", "Individual"],
["person@proton.me", "Individual"],
["person@protonmail.com", "Individual"],
["person@pm.me", "Individual"],
["person@aol.com", "Individual"],
["person@gmail.com.example.test", "gmail.com.example.test"],
])("sets Company for a new signup with %s to %s", async (email, company) => {
const crm = fixture([], email)
await syncConsoleAccount({ ...account, email }, crm.request)
expect(crm.people).toHaveLength(1)
expect(crm.people[0]).toMatchObject({ Company: company, Email: email.toLowerCase(), LeadSource: "console signup" })
})
test("matching Leads and Contacts keep their source and receive the Console fields", async () => {
const crm = fixture([
{ object: "Contact", Id: "003000000000001AAA", LeadSource: "Partner", AccountId: "001000000000001AAA" },
{ object: "Lead", Id: "00Q000000000002AAA", LeadSource: "enterprise website form", Company: "Visitor's Company" },
])
await syncConsoleAccount(account, crm.request)
expect(crm.writes.every((write) => write.method === "PATCH")).toBe(true)
expect(crm.people.map((person) => person.LeadSource)).toEqual(["Partner", "enterprise website form"])
expect(crm.people[0].AccountId).toBe("001000000000001AAA")
expect(crm.people[1].Company).toBe("Visitor's Company")
for (const write of crm.writes) {
expect(write.payload).not.toHaveProperty("Company")
expect(write.payload).not.toHaveProperty("AccountId")
}
for (const person of crm.people)
expect(person).toMatchObject({
Has_Console_Account__c: true,
Console_Account_ID__c: account.id,
Console_Signup_Date__c: account.createdAt,
})
})
test("a converted person's Contact is updated without creating another Lead", async () => {
const crm = fixture([{ object: "Contact", Id: "003000000000001AAA", LeadSource: "Website" }])
await syncConsoleAccount(account, crm.request)
expect(crm.people).toHaveLength(1)
expect(crm.writes[0].path).toContain("/Contact/")
expect(crm.people[0].LeadSource).toBe("Website")
})
test("the signup endpoint requires its server credential before touching Salesforce", async () => {
const crm = fixture()
const response = await handleConsoleSignup(
new Request("http://localhost/api/console-signup", { method: "POST", body: JSON.stringify(account) }),
"test-shared-token",
crm.request,
)
expect(response.status).toBe(401)
expect(crm.writes).toHaveLength(0)
})
test("the authenticated signup endpoint reports confirmed delivery", async () => {
const crm = fixture()
const response = await handleConsoleSignup(
new Request("http://localhost/api/console-signup", {
method: "POST",
headers: { Authorization: "Bearer test-shared-token" },
body: JSON.stringify(account),
}),
"test-shared-token",
crm.request,
)
expect(response.status).toBe(200)
expect(await response.json()).toMatchObject({ success: true })
expect(crm.people).toHaveLength(1)
})

View file

@ -1,19 +0,0 @@
import { timingSafeEqual } from "node:crypto"
import { consoleAccountSchema } from "~/lib/console-account"
import { salesforceRequest, syncConsoleAccount, type SalesforceRequest } from "~/lib/salesforce"
export async function handleConsoleSignup(request: Request, token: string, salesforce?: SalesforceRequest) {
const expected = Buffer.from(`Bearer ${token}`)
const actual = Buffer.from(request.headers.get("authorization") ?? "")
if (!token || expected.length !== actual.length || !timingSafeEqual(expected, actual))
return new Response(null, { status: 401 })
const data: unknown = await request.json().catch(() => null)
const account = consoleAccountSchema.safeParse(data)
if (!account.success) return Response.json({ error: "Invalid Console account" }, { status: 400 })
return Promise.resolve(salesforce)
.then(async (client) => syncConsoleAccount(account.data, client ?? (await salesforceRequest())))
.then(
(result) => Response.json(result),
() => Response.json({ error: "Salesforce sync unavailable" }, { status: 503 }),
)
}

View file

@ -4,7 +4,6 @@ import { i18n } from "~/i18n"
import { localeFromRequest } from "~/lib/language"
import { createLead } from "~/lib/salesforce"
import { inferenceSpendOptions } from "~/lib/inference-spend"
import { lookupConsoleAccount, type ConsoleAccount } from "~/lib/console-account"
import { verifyHcaptcha } from "~/lib/hcaptcha"
interface EnterpriseFormData {
@ -97,7 +96,6 @@ export async function handleEnterpriseRequest(
options: {
salesforceSubmit?: (payload: Record<string, unknown>) => Promise<boolean>
notifications?: boolean
accountLookup?: (email: string) => Promise<ConsoleAccount | null>
captcha?: Parameters<typeof verifyHcaptcha>[1]
} = {},
) {
@ -136,11 +134,6 @@ export async function handleEnterpriseRequest(
return Response.json({ error: dict["enterprise.form.error.captcha"] }, { status: 400 })
}
const account = await (options.accountLookup ?? lookupConsoleAccount)(body.email).catch((err) => {
console.error("Failed to look up Console account:", err)
return undefined
})
const emailContent = enterpriseEmailContent(body, inferenceSpend?.salesforceValue)
const [lead, mail, octopus] = await Promise.all([
@ -153,7 +146,6 @@ export async function handleEnterpriseRequest(
phone: body.phone,
inferenceSpend: inferenceSpend?.salesforceValue,
message: body.message,
consoleAccount: account,
},
options.salesforceSubmit,
).catch((err) => {
@ -179,10 +171,6 @@ export async function handleEnterpriseRequest(
])
if (!lead && !mail && !octopus) {
if (import.meta.env.DEV) {
console.warn("Enterprise inquiry accepted in dev mode without integrations", { email: body.email })
return Response.json({ success: true, message: dict["enterprise.form.success.submitted"] }, { status: 200 })
}
console.error("Enterprise inquiry delivery failed", { email: body.email })
return Response.json({ error: dict["enterprise.form.error.internalServer"] }, { status: 500 })
}

View file

@ -71,7 +71,6 @@ const submit = async (captcha: string | undefined, verify: () => Promise<Respons
return true
},
notifications: false,
accountLookup: async () => null,
},
)
return { status: response.status, leads }

View file

@ -1,61 +0,0 @@
import { Resource } from "@opencode/console-resource"
const paths: Record<string, string | undefined> = {
"POST /zen/v1/chat/completions": "/openai/v1/chat/completions",
"POST /zen/v1/responses": "/openai/v1/responses",
"POST /zen/v1/messages": "/anthropic/v1/messages",
"POST /zen/v1/systemone": "/systemone/v1/systemone",
"POST /zen/go/v1/chat/completions": "/go/openai/v1/chat/completions",
"POST /zen/go/v1/responses": "/go/openai/v1/responses",
"POST /zen/go/v1/messages": "/go/anthropic/v1/messages",
"POST /zen/go/v1/systemone": "/go/systemone/v1/systemone",
"GET /zen/v1/models": "/v1/models",
"GET /zen/go/v1/models": "/go/v1/models",
"GET /zen/go/v1/usage": "/go/v1/usage",
}
// New inference owns authentication, model availability, free-tier access, and billing.
export async function proxyInference(request: Request) {
const url = new URL(request.url)
const path =
paths[`${request.method} ${url.pathname}`] ??
(request.method === "POST" &&
/^\/zen\/v1\/models\/[^/]+:(?:generateContent|streamGenerateContent)$/.test(url.pathname)
? url.pathname.replace("/zen/v1/models/", "/google/v1beta/models/")
: undefined)
if (!path) return new Response(null, { status: 404 })
const key = url.pathname.endsWith("/messages")
? request.headers.get("x-api-key")
: path.startsWith("/google/")
? request.headers.get("x-goog-api-key")
: request.headers.get("authorization")?.split(" ")[1]
const destination = new URL(Resource.ConsoleMigration.inferenceUrl)
destination.pathname = `${destination.pathname.replace(/\/$/, "")}${path}`
destination.search = url.search
destination.hash = ""
const forwarded = new Request(destination, request)
for (const name of ["host", "content-length"]) forwarded.headers.delete(name)
// Keyless requests must reach new inference without credentials; `public` is not an API key there.
const anonymous = !key || key === "public"
if (anonymous) for (const name of ["authorization", "x-api-key", "x-goog-api-key"]) forwarded.headers.delete(name)
if (!anonymous) forwarded.headers.set("authorization", `Bearer ${key}`)
const ip = request.headers.get("cf-connecting-ip")
if (ip) forwarded.headers.set("x-real-ip", ip)
const requestID = request.headers.get("x-opencode-request-id") ?? request.headers.get("x-opencode-request")
if (requestID) forwarded.headers.set("x-opencode-request-id", requestID)
const response = await (Resource.INFERENCE as { fetch: typeof fetch })
.fetch(forwarded, { redirect: "manual" })
.catch(() => undefined)
if (!response)
return Response.json(
{ error: { type: "api_error", message: "Inference routing is unavailable. Please retry later." } },
{ status: 503, headers: { "Cache-Control": "no-store" } },
)
// SolidStart replaces 404 responses with its own route-not-found body, hiding the inference error.
if (response.status !== 404) return response
return new Response(response.body, { status: 400, statusText: "Bad Request", headers: response.headers })
}

View file

@ -91,12 +91,6 @@ test("the local Enterprise request handler applies the updated mapping to a form
return response.ok
},
notifications: false,
accountLookup: async () => ({
id: "user_test",
email: "fake-local-visitor@example.test",
name: null,
createdAt: "2026-10-01T12:00:00.000Z",
}),
},
)
@ -107,8 +101,5 @@ test("the local Enterprise request handler applies the updated mapping to a form
Description: " How are you bro!\nThis is a local form test. ",
Current_Monthly_Inference_Spend__c: "$50K–$100K",
LeadSource: "enterprise website form",
Has_Console_Account__c: true,
Console_Account_ID__c: "user_test",
Console_Signup_Date__c: "2026-10-01T12:00:00.000Z",
})
})

View file

@ -1,54 +1,4 @@
import { Resource } from "@opencode/console-resource"
import { z } from "zod"
import { consoleAccountFields, type ConsoleAccount } from "./console-account"
// Match provider domains exactly; business domains hosted by these providers remain business addresses.
const personalEmailDomains = new Set([
"gmail.com",
"googlemail.com",
"outlook.com",
"outlook.co.uk",
"hotmail.com",
"hotmail.co.uk",
"hotmail.fr",
"hotmail.de",
"hotmail.it",
"live.com",
"live.co.uk",
"live.ca",
"live.com.au",
"msn.com",
"yahoo.com",
"yahoo.co.uk",
"yahoo.co.in",
"yahoo.com.au",
"yahoo.ca",
"yahoo.fr",
"yahoo.de",
"yahoo.it",
"yahoo.co.jp",
"ymail.com",
"rocketmail.com",
"icloud.com",
"me.com",
"mac.com",
"proton.me",
"protonmail.com",
"pm.me",
"aol.com",
"aim.com",
"gmx.com",
"gmx.de",
"gmx.net",
"mail.com",
"fastmail.com",
"fastmail.fm",
"hey.com",
"tuta.com",
"tuta.io",
"tutanota.com",
"tutanota.de",
])
async function login() {
const url = Resource.SALESFORCE_INSTANCE_URL.value.replace(/\/$/, "")
@ -96,7 +46,6 @@ export interface SalesforceLeadInput {
phone?: string
inferenceSpend?: string
message: string
consoleAccount?: ConsoleAccount | null
}
export async function createLead(
@ -112,7 +61,6 @@ export async function createLead(
Description: input.message,
Current_Monthly_Inference_Spend__c: input.inferenceSpend,
LeadSource: "enterprise website form",
...(input.consoleAccount === undefined ? {} : consoleAccountFields(input.consoleAccount)),
}
if (submit) return submit(payload)
const auth = await login()
@ -138,65 +86,3 @@ export async function createLead(
return true
}
export type SalesforceRequest = (
path: string,
method?: "GET" | "POST" | "PATCH",
payload?: Record<string, unknown>,
) => Promise<unknown>
export async function salesforceRequest(): Promise<SalesforceRequest> {
const auth = await login()
if (!auth) throw new Error("Salesforce authentication unavailable")
return async (path, method = "GET", payload) => {
const response = await fetch(`${auth.url}${path}`, {
method,
headers: { Authorization: `Bearer ${auth.token}`, "Content-Type": "application/json" },
...(payload ? { body: JSON.stringify(payload) } : {}),
signal: AbortSignal.timeout(8000),
})
if (!response.ok) throw new Error(`Salesforce request failed (${response.status})`)
if (response.status === 204) return null
return response.json()
}
}
/** Update existing people without changing their acquisition source or sales notes. */
export async function syncConsoleAccount(account: ConsoleAccount, request: SalesforceRequest) {
const quote = (value: string) => value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")
const matches = async (object: "Contact" | "Lead") => {
const query = `SELECT Id FROM ${object} WHERE ${object === "Lead" ? "IsConverted = false AND " : ""}(Console_Account_ID__c = '${quote(account.id)}' OR Email = '${quote(account.email.toLowerCase())}')`
const records: { Id: string }[] = []
let path: string | undefined = `/services/data/v59.0/query?q=${encodeURIComponent(query)}`
while (path) {
const result = z
.object({
records: z.array(z.object({ Id: z.string().regex(/^[a-zA-Z0-9]{15,18}$/) })),
done: z.boolean(),
nextRecordsUrl: z.string().optional(),
})
.parse(await request(path))
records.push(...result.records)
if (result.done) break
if (!result.nextRecordsUrl?.startsWith("/services/data/")) throw new Error("Salesforce query pagination missing")
path = result.nextRecordsUrl
}
return records.map((record) => ({ object, id: record.Id }))
}
const people = [...(await matches("Contact")), ...(await matches("Lead"))]
const fields = consoleAccountFields(account)
for (const person of people)
await request(`/services/data/v59.0/sobjects/${person.object}/${person.id}`, "PATCH", fields)
if (people.length > 0) return { success: true, created: false, updated: people.length }
const domain = account.email.slice(account.email.lastIndexOf("@") + 1).toLowerCase()
const result = z.object({ success: z.literal(true), id: z.string() }).parse(
await request("/services/data/v59.0/sobjects/Lead", "POST", {
LastName: (account.name?.trim() || account.email).slice(0, 80),
Company: personalEmailDomains.has(domain) ? "Individual" : domain,
Email: account.email.toLowerCase(),
LeadSource: "console signup",
...fields,
}),
)
return { success: true, created: true, id: result.id }
}

View file

@ -1,5 +1,4 @@
import { createMiddleware } from "@solidjs/start/middleware"
import { Resource } from "@opencode/console-resource"
import { LOCALE_HEADER, cookie, fromPathname, strip } from "~/lib/language"
import { sanitizeServerActionRequest } from "~/lib/server-action"
@ -36,10 +35,10 @@ export default createMiddleware({
return new Response(null, { status: 301, headers: { Location: locale ? `/${locale}/go` : "/go" } })
// The legacy dashboard and its sign-in moved to the new Console.
if (/^\/(auth|workspace)(\/|$)/.test(url.pathname) && Resource.ConsoleMigration.consoleUrl)
if (/^\/(auth|workspace)(\/|$)/.test(url.pathname))
return new Response(null, {
status: 302,
headers: { Location: `${Resource.ConsoleMigration.consoleUrl}/login`, "Cache-Control": "no-store" },
headers: { Location: "https://opencode.ai/console/login", "Cache-Control": "no-store" },
})
},
})

View file

@ -1,8 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { Resource } from "@opencode/console-resource"
import { handleConsoleSignup } from "~/lib/console-signup"
// SolidStart keeps only the method export from API routes, so the handler lives in lib.
export async function POST(event: APIEvent) {
return handleConsoleSignup(event.request, Resource.CONSOLE_CRM_TOKEN.value)
}

View file

@ -1,7 +1,16 @@
import type { APIEvent } from "@solidjs/start/server"
import { Resource } from "@opencode/console-resource"
import { i18n } from "~/i18n"
import { handleEnterpriseRequest } from "~/lib/enterprise"
import { localeFromRequest } from "~/lib/language"
// SolidStart keeps only the method export from API routes, so the handler lives in lib.
export async function POST(event: APIEvent) {
// Outside production, accept submissions without reaching spam scoring, Salesforce, email, or EmailOctopus.
if (import.meta.env.DEV || Resource.App.stage !== "production")
return Response.json({
success: true,
message: i18n(localeFromRequest(event.request))["enterprise.form.success.submitted"],
})
return handleEnterpriseRequest(event.request)
}

View file

@ -1,6 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { proxyInference } from "~/lib/inference-proxy"
export function POST(input: APIEvent) {
return proxyInference(input.request)
}

View file

@ -1,6 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { proxyInference } from "~/lib/inference-proxy"
export function POST(input: APIEvent) {
return proxyInference(input.request)
}

View file

@ -1,11 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { buildOptionsResponse } from "../../util/modelsHandler"
import { proxyInference } from "~/lib/inference-proxy"
export async function OPTIONS(_input: APIEvent) {
return buildOptionsResponse()
}
export function GET(input: APIEvent) {
return proxyInference(input.request)
}

View file

@ -1,6 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { proxyInference } from "~/lib/inference-proxy"
export function POST(input: APIEvent) {
return proxyInference(input.request)
}

View file

@ -1,6 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { proxyInference } from "~/lib/inference-proxy"
export function POST(input: APIEvent) {
return proxyInference(input.request)
}

View file

@ -1,6 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { proxyInference } from "~/lib/inference-proxy"
export function GET(input: APIEvent) {
return proxyInference(input.request)
}

View file

@ -1,10 +0,0 @@
export async function buildOptionsResponse() {
return new Response(null, {
status: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
})
}

View file

@ -1,6 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { proxyInference } from "~/lib/inference-proxy"
export function POST(input: APIEvent) {
return proxyInference(input.request)
}

View file

@ -1,6 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { proxyInference } from "~/lib/inference-proxy"
export function POST(input: APIEvent) {
return proxyInference(input.request)
}

View file

@ -1,11 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { buildOptionsResponse } from "~/routes/zen/util/modelsHandler"
import { proxyInference } from "~/lib/inference-proxy"
export async function OPTIONS(_input: APIEvent) {
return buildOptionsResponse()
}
export function GET(input: APIEvent) {
return proxyInference(input.request)
}

View file

@ -1,6 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { proxyInference } from "~/lib/inference-proxy"
export function POST(input: APIEvent) {
return proxyInference(input.request)
}

View file

@ -1,6 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { proxyInference } from "~/lib/inference-proxy"
export function POST(input: APIEvent) {
return proxyInference(input.request)
}

View file

@ -1,6 +0,0 @@
import type { APIEvent } from "@solidjs/start/server"
import { proxyInference } from "~/lib/inference-proxy"
export function POST(input: APIEvent) {
return proxyInference(input.request)
}

View file

@ -13,7 +13,7 @@ Lead fields to Contact on conversion. Existing records are not backfilled.
New signup Leads use the lowercased email domain as Company (for example, `acme.com`).
Recognized personal-email domains use `Individual`. This is an exact-domain list in
`packages/console/app/src/lib/salesforce.ts`, not a legal-company-name lookup.
the Console repo (`packages/core/src/crm.ts`), not a legal-company-name lookup.
Signup sync preserves existing Lead companies and Contact account associations;
Enterprise inquiries use the company supplied by the visitor.
@ -28,11 +28,10 @@ sf project deploy start --source-dir force-app --target-org opencode --wait 10
sf org assign permset --name Console_Account_Sync --target-org opencode
```
The application integration uses the existing SST Salesforce credentials. Set the
same `CONSOLE_CRM_TOKEN` on the SST website and Console API. The SST website
links `CONSOLE_CRM_LOOKUP_URL` to the stage's private Console account lookup.
The Console writes these fields when someone signs up. Enterprise inquiry Leads from
the website no longer receive them from the website; Salesforce automation sets them.
For local development, run `bun run dev:enterprise` in `packages/console/app` with
`CONSOLE_CRM_TOKEN` set and `sf` on PATH. It serves the same Enterprise request and
signup-sync handlers on `127.0.0.1:3101`, using the CLI's authenticated transport.
It never sends local sales notifications or EmailOctopus subscriptions.
`sf` on PATH. It serves the same Enterprise request handler on `127.0.0.1:3101`, using
the CLI's authenticated transport. It never sends local sales notifications or
EmailOctopus subscriptions.