fix: route production IM API correctly
Deploy im-webapp production / deploy (push) Successful in 42s
Deploy im-webapp production / deploy (push) Successful in 42s
This commit is contained in:
@@ -41,8 +41,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
SHORT_SHA="$(printf '%.12s' "$GITHUB_SHA")"
|
SHORT_SHA="$(printf '%.12s' "$GITHUB_SHA")"
|
||||||
|
TINODE_API_KEY="${TINODE_API_KEY#\'}"
|
||||||
|
TINODE_API_KEY="${TINODE_API_KEY%\'}"
|
||||||
docker build \
|
docker build \
|
||||||
--build-arg TINODE_API_KEY="${TINODE_API_KEY}" \
|
--build-arg TINODE_API_KEY="${TINODE_API_KEY}" \
|
||||||
|
--build-arg TINODE_API_BASE="https://im-api.yuexuan.shop/v1/im" \
|
||||||
--build-arg TINODE_FILE_BASE="https://im-api.yuexuan.shop/v0/file" \
|
--build-arg TINODE_FILE_BASE="https://im-api.yuexuan.shop/v0/file" \
|
||||||
-t "${IMAGE}:pro" -t "${IMAGE}:${SHORT_SHA}" .
|
-t "${IMAGE}:pro" -t "${IMAGE}:${SHORT_SHA}" .
|
||||||
docker push "$IMAGE:pro"
|
docker push "$IMAGE:pro"
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ COPY web/. .
|
|||||||
ARG TINODE_API_KEY=
|
ARG TINODE_API_KEY=
|
||||||
ENV VITE_TINODE_API_KEY=$TINODE_API_KEY
|
ENV VITE_TINODE_API_KEY=$TINODE_API_KEY
|
||||||
|
|
||||||
|
# The production web UI and IM REST API use different origins. Local development leaves this unset
|
||||||
|
# and continues to use Vite's same-origin /v1/im proxy.
|
||||||
|
ARG TINODE_API_BASE=
|
||||||
|
ENV VITE_TINODE_API_BASE=$TINODE_API_BASE
|
||||||
|
|
||||||
# File downloads must use the canonical IM API origin. The web origin has its own independently
|
# File downloads must use the canonical IM API origin. The web origin has its own independently
|
||||||
# deployed path router and may otherwise send /v0/file requests to an older IM server.
|
# deployed path router and may otherwise send /v0/file requests to an older IM server.
|
||||||
ARG TINODE_FILE_BASE=https://api.118im.com/v0/file
|
ARG TINODE_FILE_BASE=https://api.118im.com/v0/file
|
||||||
|
|||||||
@@ -3,12 +3,16 @@ import unittest
|
|||||||
|
|
||||||
|
|
||||||
WORKFLOW = Path(__file__).resolve().parents[1] / ".gitea/workflows/deploy.yml"
|
WORKFLOW = Path(__file__).resolve().parents[1] / ".gitea/workflows/deploy.yml"
|
||||||
|
DOCKERFILE = Path(__file__).resolve().parents[1] / "Dockerfile"
|
||||||
|
API_CLIENT = Path(__file__).resolve().parents[1] / "web/src/lib/api/client.ts"
|
||||||
|
|
||||||
|
|
||||||
class DeployWorkflowContractTest(unittest.TestCase):
|
class DeployWorkflowContractTest(unittest.TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.workflow = WORKFLOW.read_text(encoding="utf-8")
|
cls.workflow = WORKFLOW.read_text(encoding="utf-8")
|
||||||
|
cls.dockerfile = DOCKERFILE.read_text(encoding="utf-8")
|
||||||
|
cls.api_client = API_CLIENT.read_text(encoding="utf-8")
|
||||||
|
|
||||||
def test_targets_pro_branch_and_single_runner(self):
|
def test_targets_pro_branch_and_single_runner(self):
|
||||||
self.assertIn("push:", self.workflow)
|
self.assertIn("push:", self.workflow)
|
||||||
@@ -35,8 +39,21 @@ class DeployWorkflowContractTest(unittest.TestCase):
|
|||||||
self.assertNotIn("git.118im.com", self.workflow)
|
self.assertNotIn("git.118im.com", self.workflow)
|
||||||
|
|
||||||
def test_uses_production_im_endpoint(self):
|
def test_uses_production_im_endpoint(self):
|
||||||
|
self.assertIn('TINODE_API_BASE="https://im-api.yuexuan.shop/v1/im"', self.workflow)
|
||||||
self.assertIn('TINODE_FILE_BASE="https://im-api.yuexuan.shop/v0/file"', self.workflow)
|
self.assertIn('TINODE_FILE_BASE="https://im-api.yuexuan.shop/v0/file"', self.workflow)
|
||||||
|
|
||||||
|
def test_build_exposes_configured_api_base_to_vite(self):
|
||||||
|
self.assertIn("ARG TINODE_API_BASE=", self.dockerfile)
|
||||||
|
self.assertIn("ENV VITE_TINODE_API_BASE=$TINODE_API_BASE", self.dockerfile)
|
||||||
|
|
||||||
|
def test_strips_accidentally_quoted_api_key_before_build(self):
|
||||||
|
self.assertIn('TINODE_API_KEY="${TINODE_API_KEY#\\\'}"', self.workflow)
|
||||||
|
self.assertIn('TINODE_API_KEY="${TINODE_API_KEY%\\\'}"', self.workflow)
|
||||||
|
|
||||||
|
def test_api_client_prefers_the_configured_production_origin(self):
|
||||||
|
self.assertIn("import.meta.env.VITE_TINODE_API_BASE?.trim()", self.api_client)
|
||||||
|
self.assertIn("CONFIGURED_API_BASE || endpoints?.api || DEFAULT_API_BASE", self.api_client)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { useSessionStore } from '@/stores/session';
|
|||||||
|
|
||||||
const DEFAULT_API_BASE = '/v1/im';
|
const DEFAULT_API_BASE = '/v1/im';
|
||||||
const DEFAULT_FILE_BASE = '/v0/file';
|
const DEFAULT_FILE_BASE = '/v0/file';
|
||||||
|
const CONFIGURED_API_BASE = import.meta.env.VITE_TINODE_API_BASE?.trim();
|
||||||
const CONFIGURED_FILE_BASE = import.meta.env.VITE_TINODE_FILE_BASE?.trim();
|
const CONFIGURED_FILE_BASE = import.meta.env.VITE_TINODE_FILE_BASE?.trim();
|
||||||
|
|
||||||
export class ImApiError extends Error {
|
export class ImApiError extends Error {
|
||||||
@@ -119,7 +120,10 @@ export function requestId(): string {
|
|||||||
|
|
||||||
export function apiBase(): string {
|
export function apiBase(): string {
|
||||||
const endpoints = useSessionStore.getState().tenant?.endpoints;
|
const endpoints = useSessionStore.getState().tenant?.endpoints;
|
||||||
return (endpoints?.api || DEFAULT_API_BASE).replace(/\/$/, '');
|
// Production serves the static web app and IM API from different origins. The configured origin
|
||||||
|
// must also work before a tenant has been resolved, while local development keeps using Vite's
|
||||||
|
// same-origin proxy by leaving this value unset.
|
||||||
|
return (CONFIGURED_API_BASE || endpoints?.api || DEFAULT_API_BASE).replace(/\/$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fileBase(): string {
|
export function fileBase(): string {
|
||||||
|
|||||||
Vendored
+1
@@ -4,6 +4,7 @@
|
|||||||
declare const __APP_VERSION__: string;
|
declare const __APP_VERSION__: string;
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_TINODE_API_BASE?: string;
|
||||||
readonly VITE_TINODE_API_KEY?: string;
|
readonly VITE_TINODE_API_KEY?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user