fix: use runtime BACKEND_URL for proxying (#411)

* fix: use runtime BACKEND_URL for proxying

Fixes #400

* chore: run formatter
This commit is contained in:
Abhishek 2026-06-03 18:47:04 +05:30 committed by GitHub
parent acc2ef9e96
commit cdb27c1d4f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 130 additions and 17 deletions

View file

@ -5,28 +5,38 @@ Revises: 6bd9f67ec994
Create Date: 2026-06-02 07:58:00.002359
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = '384be6596b36'
down_revision: Union[str, None] = '6bd9f67ec994'
revision: str = "384be6596b36"
down_revision: Union[str, None] = "6bd9f67ec994"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_users_email'), table_name='users')
op.create_index('ix_users_email_lower', 'users', [sa.literal_column('lower(email)')], unique=True, postgresql_where=sa.text('email IS NOT NULL'))
op.drop_index(op.f("ix_users_email"), table_name="users")
op.create_index(
"ix_users_email_lower",
"users",
[sa.literal_column("lower(email)")],
unique=True,
postgresql_where=sa.text("email IS NOT NULL"),
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index('ix_users_email_lower', table_name='users', postgresql_where=sa.text('email IS NOT NULL'))
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
op.drop_index(
"ix_users_email_lower",
table_name="users",
postgresql_where=sa.text("email IS NOT NULL"),
)
op.create_index(op.f("ix_users_email"), "users", ["email"], unique=True)
# ### end Alembic commands ###

View file

@ -1,6 +1,6 @@
# generated by datamodel-codegen:
# filename: dograh-openapi-rs5H7P.json
# timestamp: 2026-06-02T06:01:29+00:00
# filename: dograh-openapi-uraOZf.json
# timestamp: 2026-06-03T11:53:30+00:00
from __future__ import annotations

View file

@ -9,11 +9,6 @@ const nextConfig: NextConfig = {
},
async rewrites() {
return [
// API proxy for backend calls (excluding Next.js API routes)
{
source: "/api/:path((?!config|auth).*)*",
destination: `${process.env.BACKEND_URL || 'http://localhost:8000'}/api/:path*`,
},
{
source: "/ingest/static/:path*",
destination: "https://us-assets.i.posthog.com/static/:path*",

View file

@ -0,0 +1,104 @@
import { NextRequest, NextResponse } from "next/server";
import { getServerBackendUrl } from "@/lib/apiClient";
export const dynamic = "force-dynamic";
export const runtime = "nodejs";
const HOP_BY_HOP_HEADERS = [
"connection",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"te",
"trailer",
"transfer-encoding",
"upgrade",
];
function trimTrailingSlash(url: string) {
return url.endsWith("/") ? url.slice(0, -1) : url;
}
function buildBackendUrl(request: NextRequest) {
const backendUrl = trimTrailingSlash(getServerBackendUrl());
return `${backendUrl}${request.nextUrl.pathname}${request.nextUrl.search}`;
}
function createRequestHeaders(request: NextRequest) {
const headers = new Headers(request.headers);
for (const header of HOP_BY_HOP_HEADERS) {
headers.delete(header);
}
headers.delete("accept-encoding");
headers.delete("content-length");
headers.delete("host");
return headers;
}
function createResponseHeaders(response: Response) {
const headers = new Headers(response.headers);
const setCookies = response.headers.getSetCookie();
for (const header of HOP_BY_HOP_HEADERS) {
headers.delete(header);
}
headers.delete("content-encoding");
headers.delete("content-length");
headers.delete("set-cookie");
for (const cookie of setCookies) {
headers.append("set-cookie", cookie);
}
return headers;
}
async function getRequestBody(request: NextRequest) {
if (request.method === "GET" || request.method === "HEAD") {
return undefined;
}
return request.arrayBuffer();
}
async function proxyRequest(request: NextRequest) {
const backendUrl = buildBackendUrl(request);
try {
const response = await fetch(backendUrl, {
method: request.method,
headers: createRequestHeaders(request),
body: await getRequestBody(request),
cache: "no-store",
});
return new Response(request.method === "HEAD" ? null : response.body, {
status: response.status,
statusText: response.statusText,
headers: createResponseHeaders(response),
});
} catch (error) {
const message =
error instanceof Error ? error.message : "Unknown backend proxy error";
return NextResponse.json(
{
detail: `Backend request failed while proxying to ${backendUrl}: ${message}`,
},
{ status: 502 },
);
}
}
export const GET = proxyRequest;
export const POST = proxyRequest;
export const PUT = proxyRequest;
export const PATCH = proxyRequest;
export const DELETE = proxyRequest;
export const OPTIONS = proxyRequest;
export const HEAD = proxyRequest;

View file

@ -1,5 +1,7 @@
import "server-only";
import { getServerBackendUrl } from "@/lib/apiClient";
let cachedAuthProvider: string | null = null;
/**
@ -12,7 +14,7 @@ export async function getAuthProvider(): Promise<string> {
}
try {
const backendUrl = process.env.BACKEND_URL || "http://localhost:8000";
const backendUrl = getServerBackendUrl();
const res = await fetch(`${backendUrl}/api/v1/health`, {
next: { revalidate: 300 },
});

View file

@ -1,6 +1,8 @@
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { getServerBackendUrl } from '@/lib/apiClient';
const OSS_TOKEN_COOKIE = 'dograh_auth_token';
// Paths that don't require authentication in OSS mode
@ -14,7 +16,7 @@ async function fetchAuthProvider(): Promise<string> {
}
try {
const backendUrl = process.env.BACKEND_URL || 'http://localhost:8000';
const backendUrl = getServerBackendUrl();
const res = await fetch(`${backendUrl}/api/v1/health`);
if (res.ok) {
const data = await res.json();