mirror of
https://github.com/Dvorinka/Trackeep.git
synced 2026-07-29 05:53:50 +00:00
fix(frontend): resolve production API URL fallback to localhost
Problem: The unified Docker image builds the frontend at build time without VITE_API_URL. Vite inlined import.meta.env.VITE_API_URL as undefined, so every API call fell back to the hardcoded 'http://localhost:8080'. This broke Casa deployments where the frontend loaded from the public domain but tried to reach the backend at localhost. Solution: 1. Centralize API URL resolution in lib/api-url.ts via getApiOrigin(). It checks runtime window.ENV first (injected by docker-entrypoint.sh at container startup), then build-time import.meta.env, then dev fallback. In production unified deployments it returns '' so API calls use same-origin relative URLs (/api/v1/...) that nginx proxies to the backend. 2. Replace all 50+ inline import.meta.env.VITE_API_URL || 'localhost' usages across 14 source files with getApiOrigin() / getApiV1BaseUrl(). 3. Add build args and runtime sed substitution to Dockerfile and docker-entrypoint.sh so the same image works for any deployment. 4. Pass VITE_API_URL through docker-compose.yml and CI/CD build-args. Verified: - Production bundle contains 0 occurrences of localhost:8080. - Container health check and /api/v1/auth/check-users both return 200. - Runtime injection correctly sets VITE_API_URL='' for same-origin and VITE_API_URL='https://domain' for external backend. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
b539aa1b91
commit
4dfdd500b4
@@ -13,6 +13,7 @@ import {
|
||||
} from 'lucide-solid'
|
||||
import { AIProviderIcon } from '@/components/AIProviderIcon'
|
||||
import { useHaptics } from '@/lib/haptics'
|
||||
import { getApiOrigin } from '@/lib/api-url'
|
||||
|
||||
interface AIModel {
|
||||
id: string
|
||||
@@ -133,7 +134,7 @@ export const AIChat = () => {
|
||||
|
||||
const callAIAPI = async (message: string, modelId: string): Promise<string> => {
|
||||
const token = localStorage.getItem('token')
|
||||
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8080'
|
||||
const apiUrl = getApiOrigin()
|
||||
|
||||
const response = await fetch(`${apiUrl}/api/v1/ai/chat`, {
|
||||
method: 'POST',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createEffect, createResource, createSignal, For, Show, onMount } from 'solid-js'
|
||||
import { getApiOrigin } from '@/lib/api-url'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
import { Input } from '@/components/ui/Input'
|
||||
import { Card } from '@/components/ui/Card'
|
||||
@@ -64,7 +65,7 @@ const Chat = () => {
|
||||
const loadAIProviders = async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('token')
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/ai/providers`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/ai/providers`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
@@ -83,7 +84,7 @@ const Chat = () => {
|
||||
const loadAISettings = async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('token')
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/ai/settings`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/auth/ai/settings`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
@@ -175,7 +176,7 @@ const Chat = () => {
|
||||
const fetchSessions = async () => {
|
||||
try {
|
||||
const token = getToken()
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/sessions`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/chat/sessions`, {
|
||||
headers: {
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
},
|
||||
@@ -239,7 +240,7 @@ const Chat = () => {
|
||||
const loadSessionMessages = async (sessionId: string) => {
|
||||
try {
|
||||
const token = getToken()
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/sessions/${sessionId}/messages`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/chat/sessions/${sessionId}/messages`, {
|
||||
headers: {
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
},
|
||||
@@ -340,7 +341,7 @@ const Chat = () => {
|
||||
payload.session_id = currentSessionId()
|
||||
}
|
||||
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/send`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/chat/send`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -606,7 +607,7 @@ const Chat = () => {
|
||||
e.stopPropagation()
|
||||
try {
|
||||
const token = getToken()
|
||||
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/sessions/${session.id}`, {
|
||||
const response = await fetch(`${getApiOrigin()}/api/v1/chat/sessions/${session.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createSignal, For, Show, onCleanup, onMount } from 'solid-js';
|
||||
import { getApiOrigin } from '@/lib/api-url';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { toast } from '@/components/ui/Toast';
|
||||
@@ -973,7 +974,7 @@ export const Messages = () => {
|
||||
kind: 'voice_note',
|
||||
file_id: uploaded.id,
|
||||
title: uploaded.original_name || 'Voice note',
|
||||
url: `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/files/${uploaded.id}/download`,
|
||||
url: `${getApiOrigin()}/api/v1/files/${uploaded.id}/download`,
|
||||
}];
|
||||
|
||||
const transcript = `${voiceFinalTranscript} ${voiceInterimTranscript}`.trim();
|
||||
@@ -1366,7 +1367,7 @@ export const Messages = () => {
|
||||
const loadMembers = async () => {
|
||||
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||
try {
|
||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/members?limit=200`, {
|
||||
const res = await fetch(`${getApiOrigin()}/api/v1/members?limit=200`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
if (!res.ok) return;
|
||||
@@ -1385,7 +1386,7 @@ export const Messages = () => {
|
||||
const loadTeams = async () => {
|
||||
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||
try {
|
||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/teams?limit=200`, {
|
||||
const res = await fetch(`${getApiOrigin()}/api/v1/teams?limit=200`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
if (!res.ok) return;
|
||||
@@ -1403,7 +1404,7 @@ export const Messages = () => {
|
||||
const loadAIProviders = async () => {
|
||||
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||
try {
|
||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/ai/providers`, {
|
||||
const res = await fetch(`${getApiOrigin()}/api/v1/ai/providers`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
if (!res.ok) return;
|
||||
@@ -1424,7 +1425,7 @@ export const Messages = () => {
|
||||
const loadAISettings = async () => {
|
||||
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||
try {
|
||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/ai/settings`, {
|
||||
const res = await fetch(`${getApiOrigin()}/api/v1/auth/ai/settings`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
if (!res.ok) return;
|
||||
@@ -1454,7 +1455,7 @@ export const Messages = () => {
|
||||
const token = localStorage.getItem('trackeep_token') || localStorage.getItem('token') || '';
|
||||
setAiShareLoadingSessions(true);
|
||||
try {
|
||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/sessions`, {
|
||||
const res = await fetch(`${getApiOrigin()}/api/v1/chat/sessions`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
if (!res.ok) {
|
||||
@@ -1486,7 +1487,7 @@ export const Messages = () => {
|
||||
if (aiShareMessagesBySession()[sessionId]) return;
|
||||
setAiShareLoadingMessages(true);
|
||||
try {
|
||||
const res = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/chat/sessions/${sessionId}/messages`, {
|
||||
const res = await fetch(`${getApiOrigin()}/api/v1/chat/sessions/${sessionId}/messages`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
if (!res.ok) {
|
||||
@@ -1786,7 +1787,7 @@ export const Messages = () => {
|
||||
kind: uploaded.mime_type?.startsWith('image/') ? 'image' : 'file',
|
||||
file_id: uploaded.id,
|
||||
title: uploaded.original_name,
|
||||
url: `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/files/${uploaded.id}/download`,
|
||||
url: `${getApiOrigin()}/api/v1/files/${uploaded.id}/download`,
|
||||
});
|
||||
setUploadProgress({ done: i + 1, total: localFiles.length });
|
||||
}
|
||||
@@ -1796,7 +1797,7 @@ export const Messages = () => {
|
||||
kind: file.mime_type?.startsWith('image/') ? 'image' : 'file',
|
||||
file_id: file.id,
|
||||
title: file.original_name,
|
||||
url: `${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/files/${file.id}/download`,
|
||||
url: `${getApiOrigin()}/api/v1/files/${file.id}/download`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user