Compare commits
25 commits
main
...
high-perfo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
494384e6c6 | ||
|
|
46b486fb52 | ||
|
|
e856eae414 | ||
|
|
b55f69ffc4 | ||
|
|
fd75e7a5ea | ||
|
|
7a7849f5a6 | ||
|
|
c10668aa7d | ||
|
|
a4cae251d9 | ||
|
|
abf4095bf2 | ||
|
|
d124a91dea | ||
|
|
ef9fed75cb | ||
|
|
3c437b61b3 | ||
|
|
4fc874a44b | ||
|
|
4c61bcaf31 | ||
|
|
d8467d1aeb | ||
|
|
cf66611750 | ||
|
|
8a8072b305 | ||
|
|
5d10025c34 | ||
|
|
93deed0d11 | ||
|
|
c8ecb6bf81 | ||
|
|
6666cc5f67 | ||
|
|
aabfacc735 | ||
|
|
31101db955 | ||
|
|
4930a24e35 | ||
|
|
b21c3e43b1 |
32 changed files with 385 additions and 328 deletions
13
.forgejo/scripts/kaniko-build.sh
Executable file
13
.forgejo/scripts/kaniko-build.sh
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
#!/busybox/sh
|
||||
set -eu
|
||||
|
||||
mkdir -p /kaniko/.docker
|
||||
cat > /kaniko/.docker/config.json <<JSON
|
||||
{"auths":{"${REGISTRY_HOSTPORT}":{"username":"${REGISTRY_USERNAME}","password":"${REGISTRY_PASSWORD}"}}}
|
||||
JSON
|
||||
|
||||
/kaniko/executor \
|
||||
--context "${GITHUB_WORKSPACE}" \
|
||||
--dockerfile "${GITHUB_WORKSPACE}/Dockerfile" \
|
||||
--destination "${REGISTRY_HOSTPORT}/${IMAGE_NAME}:${COMMIT_SHA}" \
|
||||
--destination "${REGISTRY_HOSTPORT}/${IMAGE_NAME}:${LATEST_TAG}"
|
||||
|
|
@ -11,7 +11,7 @@ Usage:
|
|||
This script:
|
||||
1. Updates the newTag for the specified service to the SHA
|
||||
2. Commits and pushes to the gitops repo
|
||||
3. ArgoCD detects the change and deploys
|
||||
3. Flux detects the change and deploys
|
||||
"""
|
||||
|
||||
import argparse
|
||||
|
|
@ -98,10 +98,14 @@ def main():
|
|||
image_name = f"nxtgauge-{args.service}"
|
||||
|
||||
# Find the right kustomization file based on service
|
||||
if "frontend" in args.service or "admin" in args.service:
|
||||
if "frontend" in args.service:
|
||||
kustomization_path = os.path.join(args.repo, "apps/nxtgauge-frontend-solid/overlays/prod/kustomization.yaml")
|
||||
if not os.path.exists(kustomization_path):
|
||||
kustomization_path = os.path.join(args.repo, "apps/nxtgauge-frontend-solid/base/kustomization.yaml")
|
||||
elif "admin" in args.service:
|
||||
kustomization_path = os.path.join(args.repo, "apps/nxtgauge-admin-solid/overlays/prod/kustomization.yaml")
|
||||
if not os.path.exists(kustomization_path):
|
||||
kustomization_path = os.path.join(args.repo, "apps/nxtgauge-admin-solid/base/kustomization.yaml")
|
||||
elif "ai-assistant" in args.service:
|
||||
kustomization_path = os.path.join(args.repo, "apps/nxtgauge-ai-assistant/overlays/prod/kustomization.yaml")
|
||||
if not os.path.exists(kustomization_path):
|
||||
75
.forgejo/workflows/build.yaml
Normal file
75
.forgejo/workflows/build.yaml
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
name: build-and-push
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- high-performance
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker://gcr.io/kaniko-project/executor:v1.23.2-debug
|
||||
env:
|
||||
REGISTRY_HOSTPORT: ${{ secrets.REGISTRY_HOSTPORT }}
|
||||
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
IMAGE_NAME: nxtgauge-frontend-solid
|
||||
LATEST_TAG: high-performance-latest
|
||||
COMMIT_SHA: ${{ github.sha }}
|
||||
with:
|
||||
entrypoint: /busybox/sh
|
||||
args: ${{ github.workspace }}/.forgejo/scripts/kaniko-build.sh
|
||||
|
||||
- name: Prune old image tags (keep latest 1 SHA)
|
||||
if: success()
|
||||
continue-on-error: true
|
||||
env:
|
||||
REGISTRY_HOST: ${{ secrets.REGISTRY_HOSTPORT }}
|
||||
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 .forgejo/scripts/registry_prune.py \
|
||||
--registry "$REGISTRY_HOST" \
|
||||
--repo "nxtgauge-frontend-solid" \
|
||||
--username "$REGISTRY_USERNAME" \
|
||||
--password "$REGISTRY_PASSWORD" \
|
||||
--keep 1
|
||||
|
||||
- name: Update GitOps and trigger deployment
|
||||
if: success()
|
||||
continue-on-error: true
|
||||
env:
|
||||
GITEOPS_REPO: ${{ secrets.GITEOPS_REPO }}
|
||||
GITEOPS_SSH_KEY: ${{ secrets.GITEOPS_SSH_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SHA="$(git rev-parse HEAD)"
|
||||
|
||||
if [ -z "$GITEOPS_REPO" ]; then
|
||||
echo "GITEOPS_REPO secret not set, skipping GitOps update"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
GITEOPS_DIR=$(mktemp -d)
|
||||
git clone "$GITEOPS_REPO" "$GITEOPS_DIR"
|
||||
cd "$GITEOPS_DIR"
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
echo "$GITEOPS_SSH_KEY" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null
|
||||
|
||||
python3 .forgejo/scripts/update-gitops.py \
|
||||
--repo "$GITEOPS_DIR" \
|
||||
--service "frontend-solid" \
|
||||
--sha "${SHA}" \
|
||||
--message "chore: deploy frontend-solid@${SHA}"
|
||||
|
||||
rm -rf "$GITEOPS_DIR"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
name: build-and-push
|
||||
name: Build Frontend And Update GitOps
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -15,97 +15,57 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
- name: Install Docker CLI
|
||||
run: |
|
||||
export DOCKER_HOST=unix:///var/run/docker.sock
|
||||
docker version
|
||||
docker buildx create --use || true
|
||||
docker buildx inspect --bootstrap
|
||||
apt-get update
|
||||
apt-get install -y docker.io
|
||||
|
||||
- name: Login to Registry
|
||||
env:
|
||||
REGISTRY_HOSTPORT: ${{ secrets.REGISTRY_HOSTPORT }}
|
||||
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
- name: Log in to registry
|
||||
run: |
|
||||
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.nxtgauge.com -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin
|
||||
|
||||
- name: Build and push frontend image
|
||||
run: |
|
||||
set -euo pipefail
|
||||
export DOCKER_HOST=unix:///var/run/docker.sock
|
||||
test -n "$REGISTRY_HOSTPORT"
|
||||
echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_HOSTPORT" -u "$REGISTRY_USERNAME" --password-stdin
|
||||
IMAGE="registry.nxtgauge.com/nxtgauge-frontend-solid:${{ github.sha }}"
|
||||
docker build -t "${IMAGE}" -t registry.nxtgauge.com/nxtgauge-frontend-solid:latest .
|
||||
docker push "${IMAGE}"
|
||||
docker push registry.nxtgauge.com/nxtgauge-frontend-solid:latest
|
||||
|
||||
- name: Build and push
|
||||
update-gitops:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Update GitOps frontend tag
|
||||
env:
|
||||
REGISTRY_HOSTPORT: ${{ secrets.REGISTRY_HOSTPORT }}
|
||||
GITOPS_USERNAME: ${{ secrets.GITOPS_GITHUB_USERNAME || 'Traceworks2023' }}
|
||||
GITOPS_PASSWORD: ${{ secrets.GITOPS_GITHUB_TOKEN || secrets.GITOPS_PAT }}
|
||||
GITOPS_REPO: https://github.com/Traceworks2023/nxtgauge-gitops.git
|
||||
IMAGE_TAG: ${{ github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
export DOCKER_HOST=unix:///var/run/docker.sock
|
||||
|
||||
build_and_push() {
|
||||
docker buildx build --push \
|
||||
-f Dockerfile \
|
||||
-t "$REGISTRY_HOSTPORT/nxtgauge-frontend-solid:${{ gitea.sha }}" \
|
||||
-t "$REGISTRY_HOSTPORT/nxtgauge-frontend-solid:high-performance-latest" \
|
||||
.
|
||||
}
|
||||
|
||||
for attempt in 1 2 3; do
|
||||
echo "Build attempt $attempt"
|
||||
if build_and_push; then
|
||||
exit 0
|
||||
fi
|
||||
echo "Build attempt $attempt failed; recreating builder and retrying"
|
||||
docker buildx rm --all-inactive --force || true
|
||||
docker buildx create --use || true
|
||||
docker buildx inspect --bootstrap
|
||||
sleep $((attempt * 10))
|
||||
done
|
||||
|
||||
echo "Build failed after retries"
|
||||
exit 1
|
||||
|
||||
- name: Prune old image tags (keep latest 1 SHA)
|
||||
if: success()
|
||||
continue-on-error: true
|
||||
env:
|
||||
REGISTRY_HOST: ${{ secrets.REGISTRY_HOSTPORT }}
|
||||
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
||||
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 .gitea/scripts/registry_prune.py \
|
||||
--registry "$REGISTRY_HOST" \
|
||||
--repo "nxtgauge-frontend-solid" \
|
||||
--username "$REGISTRY_USERNAME" \
|
||||
--password "$REGISTRY_PASSWORD" \
|
||||
--keep 1
|
||||
|
||||
- name: Update GitOps and trigger deployment
|
||||
if: success()
|
||||
continue-on-error: true
|
||||
env:
|
||||
GITEOPS_REPO: ${{ secrets.GITEOPS_REPO }}
|
||||
GITEOPS_SSH_KEY: ${{ secrets.GITEOPS_SSH_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "$GITEOPS_REPO" ]; then
|
||||
echo "GITEOPS_REPO secret not set, skipping GitOps update"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
GITEOPS_DIR=$(mktemp -d)
|
||||
git clone "$GITEOPS_REPO" "$GITEOPS_DIR"
|
||||
cd "$GITEOPS_DIR"
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
echo "$GITEOPS_SSH_KEY" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null
|
||||
|
||||
python3 .gitea/scripts/update-gitops.py \
|
||||
--repo "$GITEOPS_DIR" \
|
||||
--service "frontend-solid" \
|
||||
--sha "${{ gitea.sha }}" \
|
||||
--message "chore: deploy frontend-solid@${{ gitea.sha }}"
|
||||
|
||||
rm -rf "$GITEOPS_DIR"
|
||||
test -n "${GITOPS_PASSWORD:-}" || { echo "GITOPS_PASSWORD is empty"; exit 1; }
|
||||
AUTH="$(printf '%s' "${GITOPS_USERNAME}:${GITOPS_PASSWORD}" | base64 -w0)"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
git -c http.extraHeader="AUTHORIZATION: basic ${AUTH}" clone --branch main "${GITOPS_REPO}" "${TMP_DIR}"
|
||||
cd "${TMP_DIR}"
|
||||
python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
import os
|
||||
path = Path('apps/nxtgauge-frontend-solid/overlays/prod/kustomization.yaml')
|
||||
lines = path.read_text().splitlines()
|
||||
out = []
|
||||
for line in lines:
|
||||
if line.strip().startswith('newTag:'):
|
||||
indent = line[:len(line) - len(line.lstrip())]
|
||||
out.append(f"{indent}newTag: {os.environ['IMAGE_TAG']}")
|
||||
else:
|
||||
out.append(line)
|
||||
path.write_text('\n'.join(out) + '\n')
|
||||
PY
|
||||
git config user.name "forgejo-actions"
|
||||
git config user.email "forgejo-actions@nxtgauge.com"
|
||||
git add apps/nxtgauge-frontend-solid/overlays/prod/kustomization.yaml
|
||||
git diff --cached --quiet && exit 0
|
||||
git commit -m "chore(gitops): update frontend image to ${IMAGE_TAG}"
|
||||
git -c http.extraHeader="AUTHORIZATION: basic ${AUTH}" push origin main
|
||||
|
|
|
|||
40
.github/workflows/sync-to-forgejo.yml
vendored
Normal file
40
.github/workflows/sync-to-forgejo.yml
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
name: sync-to-forgejo
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- high-performance
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Push branch to Forgejo
|
||||
env:
|
||||
FORGEJO_SECRET: ${{ secrets.FORGEJO_SECRET || secrets.GITEA_SECRET }}
|
||||
FORGEJO_OWNER: ${{ secrets.FORGEJO_OWNER || 'ashwin' }}
|
||||
FORGEJO_USERNAME: ${{ secrets.FORGEJO_USERNAME || secrets.GITEA_USERNAME || 'ashwin' }}
|
||||
REPO: ${{ github.event.repository.name }}
|
||||
BRANCH: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -n "${FORGEJO_SECRET:-}" || { echo "FORGEJO_SECRET is empty"; exit 1; }
|
||||
|
||||
ENCODED_PASSWORD="$(printf '%s' "${FORGEJO_SECRET}" | jq -sRr @uri)"
|
||||
TARGET="https://${FORGEJO_USERNAME}:${ENCODED_PASSWORD}@ci.nxtgauge.com/${FORGEJO_OWNER}/${REPO}.git"
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git remote remove forgejo 2>/dev/null || true
|
||||
git remote add forgejo "${TARGET}"
|
||||
|
||||
git push forgejo "HEAD:${BRANCH}" --force
|
||||
git push forgejo --tags --force
|
||||
46
.github/workflows/sync-to-gitea.yml
vendored
46
.github/workflows/sync-to-gitea.yml
vendored
|
|
@ -1,46 +0,0 @@
|
|||
name: sync-to-gitea
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- high-performance
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Sync to Gitea
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_SECRET }}
|
||||
REPO: ${{ github.event.repository.name }}
|
||||
BRANCH: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
export GIT_TERMINAL_PROMPT=0
|
||||
export GIT_TRACE=1
|
||||
export GIT_CURL_VERBOSE=1
|
||||
|
||||
USER="Admin"
|
||||
TARGET="https://ci.nxtgauge.com/Admin/${REPO}.git"
|
||||
AUTH="$(printf '%s' "${USER}:${GITEA_TOKEN}" | base64 -w0)"
|
||||
|
||||
test -n "${GITEA_TOKEN:-}" || (echo "GITEA_TOKEN empty" && exit 1)
|
||||
curl -fsS -H "Authorization: token ${GITEA_TOKEN}" https://ci.nxtgauge.com/api/v1/user >/dev/null
|
||||
curl -fsS -H "Authorization: Basic ${AUTH}" "${TARGET}/info/refs?service=git-receive-pack" >/dev/null
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --global http.version HTTP/1.1
|
||||
git config --global http.postBuffer 524288000
|
||||
git remote remove gitea 2>/dev/null || true
|
||||
git remote add gitea "${TARGET}"
|
||||
|
||||
git -c http.extraheader="Authorization: Basic ${AUTH}" push gitea "HEAD:${BRANCH}" --force
|
||||
git -c http.extraheader="Authorization: Basic ${AUTH}" push gitea --tags --force
|
||||
|
|
@ -12,3 +12,6 @@ See `docs/MIGRATION_MASTER_PLAN.md` for the staged plan.
|
|||
Required secrets:
|
||||
- `REGISTRY_USERNAME`
|
||||
- `REGISTRY_PASSWORD`
|
||||
# Mon Jun 8 09:06:25 PM IST 2026
|
||||
# Mon Jun 8 09:16:18 PM IST 2026
|
||||
|
||||
|
|
|
|||
|
|
@ -102,9 +102,9 @@ Visual tests compare screenshots against baselines in `tests/e2e/visual/`.
|
|||
|
||||
## CI / Nightly Runs
|
||||
|
||||
GitHub Actions runs tests nightly via `.gitea/workflows/test.yaml`:
|
||||
GitHub Actions runs tests nightly via `.forgejo/workflows/test.yaml`:
|
||||
- **2:30 AM daily** — all test suites
|
||||
- **On-demand** — use `workflow_dispatch` trigger in Gitea
|
||||
- **On-demand** — use `workflow_dispatch` trigger in Forgejo
|
||||
|
||||
Artifacts are uploaded:
|
||||
- `vitest-coverage/` — coverage reports
|
||||
|
|
|
|||
|
|
@ -21,53 +21,52 @@ export default function CaptchaCanvas(props: CaptchaCanvasProps) {
|
|||
|
||||
const width = 176;
|
||||
const height = 52;
|
||||
const dpr = typeof window !== 'undefined' ? Math.max(1, window.devicePixelRatio || 1) : 1;
|
||||
|
||||
// Set canvas resolution first (before any drawing)
|
||||
canvas.width = Math.floor(width * dpr);
|
||||
canvas.height = Math.floor(height * dpr);
|
||||
// Set canvas resolution (fixed at 1x to prevent zoom issues)
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
canvas.style.width = `${width}px`;
|
||||
canvas.style.height = `${height}px`;
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
|
||||
// Clear and fill background
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
// Draw decorative lines
|
||||
// Draw decorative lines (within bounds)
|
||||
for (let i = 0; i < 2; i += 1) {
|
||||
ctx.strokeStyle = i % 2 === 0 ? 'rgba(253,98,22,0.16)' : 'rgba(27,36,64,0.14)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(Math.random() * canvas.width, Math.random() * canvas.height);
|
||||
ctx.lineTo(Math.random() * canvas.width, Math.random() * canvas.height);
|
||||
ctx.moveTo(Math.random() * width, Math.random() * height);
|
||||
ctx.lineTo(Math.random() * width, Math.random() * height);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Draw decorative circles
|
||||
// Draw decorative circles (within bounds)
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
ctx.fillStyle = i % 2 === 0 ? 'rgba(253,98,22,0.10)' : 'rgba(27,36,64,0.09)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(Math.random() * canvas.width, Math.random() * canvas.height, Math.random() * 1.8 + 0.6, 0, Math.PI * 2);
|
||||
ctx.arc(Math.random() * width, Math.random() * height, Math.random() * 1.8 + 0.6, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Draw characters
|
||||
// Draw characters (fixed positioning)
|
||||
const chars = String(props.code || '').slice(0, 6).split('');
|
||||
const startX = 16 * dpr;
|
||||
const charGap = 24 * dpr;
|
||||
const startX = 16;
|
||||
const charGap = 24;
|
||||
|
||||
chars.forEach((char, index) => {
|
||||
const x = startX + index * charGap;
|
||||
const y = canvas.height / 2;
|
||||
const y = height / 2;
|
||||
const rotation = 0;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(x, y);
|
||||
ctx.rotate(rotation);
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.font = `800 ${22 * dpr}px "Courier New", monospace`;
|
||||
ctx.font = `800 22px "Courier New", monospace`;
|
||||
ctx.fillStyle = index % 2 === 0 ? '#0f172a' : '#c2410c';
|
||||
ctx.lineWidth = 0;
|
||||
ctx.fillText(char, 0, 0);
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ export default function DashboardLayout(props: ParentProps) {
|
|||
const token = sessionStorage.getItem("nxtgauge_access_token");
|
||||
if (token) {
|
||||
try {
|
||||
const res = await fetch("/api/auth/session", {
|
||||
const res = await fetch("/api/gateway/auth/session", {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ export default function RoleLandingPage(props: Props) {
|
|||
return item;
|
||||
});
|
||||
|
||||
const canonical = createMemo(() => `https://test121.nxtgauge.com${props.pathBase}/${encodeURIComponent(String(props.slug || ''))}`);
|
||||
const canonical = createMemo(() => `https://test111.nxtgauge.com${props.pathBase}/${encodeURIComponent(String(props.slug || ''))}`);
|
||||
const pageTitle = createMemo(() => (content() ? `${content()!.shortTitle} | Nxtgauge` : 'Role | Nxtgauge'));
|
||||
const pageDescription = createMemo(() =>
|
||||
content() ? `${content()!.heroDescription} Most role reviews complete in 24-48 hours.` : 'Role landing page on Nxtgauge.'
|
||||
|
|
|
|||
|
|
@ -1054,7 +1054,7 @@ export default function DashboardDesignPreview(props: {
|
|||
try {
|
||||
const token = getToken();
|
||||
if (!token) return;
|
||||
const res = await fetch('/api/me/notifications/unread-count', {
|
||||
const res = await fetch('/api/gateway/me/notifications/unread-count', {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
credentials: 'include',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -267,14 +267,14 @@ export default function MyDashboardPage(props: Props) {
|
|||
try {
|
||||
if (roleKey === 'COMPANY') {
|
||||
const [jobsRes, appsRes] = await Promise.all([
|
||||
fetch('/api/companies/jobs?page=1&limit=100', {
|
||||
fetch('/api/gateway/companies/jobs?page=1&limit=100', {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${window.sessionStorage.getItem('nxtgauge_access_token') || ''}`,
|
||||
},
|
||||
}),
|
||||
fetch('/api/companies/jobs?page=1&limit=1', {
|
||||
fetch('/api/gateway/companies/jobs?page=1&limit=1', {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
@ -293,7 +293,7 @@ export default function MyDashboardPage(props: Props) {
|
|||
);
|
||||
if (!jobsRes.ok && !appsRes.ok) setErr('Some company metrics could not be loaded.');
|
||||
} else if (roleKey === 'CUSTOMER') {
|
||||
const res = await fetch('/api/customers/requirements?page=1&limit=100', {
|
||||
const res = await fetch('/api/gateway/customers/requirements?page=1&limit=100', {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
@ -311,14 +311,14 @@ export default function MyDashboardPage(props: Props) {
|
|||
if (!res.ok) setErr('Some customer metrics could not be loaded.');
|
||||
} else if (roleKey === 'JOB_SEEKER') {
|
||||
const [jobsRes, appsRes] = await Promise.all([
|
||||
fetch('/api/jobseeker/jobs?page=1&limit=100', {
|
||||
fetch('/api/gateway/jobseeker/jobs?page=1&limit=100', {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${window.sessionStorage.getItem('nxtgauge_access_token') || ''}`,
|
||||
},
|
||||
}),
|
||||
fetch('/api/jobseeker/applications?page=1&limit=100', {
|
||||
fetch('/api/gateway/jobseeker/applications?page=1&limit=100', {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ async function fetchSession(): Promise<AuthUser | null> {
|
|||
const token = getToken();
|
||||
if (!token) return null;
|
||||
try {
|
||||
const res = await fetch("/api/auth/session", {
|
||||
const res = await fetch("/api/gateway/auth/session", {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export async function fetchHelpCenterArticles(input: {
|
|||
// Fallback: when backend search returns sparse/empty data, apply local filtering
|
||||
// so users can still find articles by simple keywords.
|
||||
if (input.q && items.length === 0) {
|
||||
const allRes = await fetch("/api/kb/articles");
|
||||
const allRes = await fetch("/api/gateway/kb/articles");
|
||||
if (allRes.ok) {
|
||||
const allData = await allRes.json();
|
||||
const allRaw: any[] = Array.isArray(allData) ? allData : (allData.articles ?? []);
|
||||
|
|
@ -62,7 +62,7 @@ export async function fetchHelpCenterArticles(input: {
|
|||
|
||||
export async function fetchHelpCenterCategories(): Promise<HelpCategory[]> {
|
||||
try {
|
||||
const res = await fetch("/api/kb/categories");
|
||||
const res = await fetch("/api/gateway/kb/categories");
|
||||
if (!res.ok) return HELP_CENTER_SEED_CATEGORIES;
|
||||
const data = await res.json();
|
||||
const raw: any[] = Array.isArray(data) ? data : (data.categories ?? []);
|
||||
|
|
@ -98,7 +98,7 @@ export async function fetchRelatedArticles(input: {
|
|||
limit?: number;
|
||||
}): Promise<HelpArticle[]> {
|
||||
try {
|
||||
const res = await fetch("/api/kb/articles");
|
||||
const res = await fetch("/api/gateway/kb/articles");
|
||||
if (!res.ok)
|
||||
return pickRelated(
|
||||
HELP_CENTER_SEED_ARTICLES as HelpArticle[],
|
||||
|
|
|
|||
147
src/middleware.ts
Normal file
147
src/middleware.ts
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/**
|
||||
* SolidStart server middleware (runs on every request).
|
||||
*
|
||||
* Workaround for a Vinxi 0.5.7 + @solidjs/start 1.3.2 build issue where
|
||||
* file-based API routes in `src/routes/api/*` are registered in the page
|
||||
* router tree but never mounted as Nitro handlers, so every `/api/*`
|
||||
* request returns a 404 from the SolidStart page renderer.
|
||||
*
|
||||
* This middleware intercepts `/api/*` paths at the SolidStart middleware
|
||||
* layer (which IS in the request pipeline) and proxies them to the Rust
|
||||
* gateway.
|
||||
*
|
||||
* Responsibilities:
|
||||
* - /api/gateway/*path → proxy to Rust gateway
|
||||
* - /api/kb/categories → proxy to Rust gateway
|
||||
* - /api/kb/articles → proxy to Rust gateway
|
||||
* - /api/kb/articles/:slug → proxy to Rust gateway
|
||||
*
|
||||
* Uses the @solidjs/start `createMiddleware` pattern, which Vinxi/h3 will
|
||||
* actually invoke via the `onRequest` hook.
|
||||
*/
|
||||
|
||||
import { createMiddleware } from "@solidjs/start/middleware";
|
||||
|
||||
const GATEWAY_URL = (
|
||||
process.env.GATEWAY_URL || "http://nxtgauge-rust-gateway:9100"
|
||||
).replace(/\/+$/, "");
|
||||
|
||||
const PUBLIC_API_URL = (
|
||||
process.env.PUBLIC_API_URL ||
|
||||
process.env.NEXT_PUBLIC_API_URL ||
|
||||
`${GATEWAY_URL}/api`
|
||||
).replace(/\/+$/, "");
|
||||
|
||||
function buildUpstream(path: string, query: string = ""): string {
|
||||
// PUBLIC_API_URL ends with /api; path starts with /api/...
|
||||
// Strip the /api prefix from path so we don't double up.
|
||||
if (PUBLIC_API_URL.endsWith("/api")) {
|
||||
const stripped = path.replace(/^\/api/, "");
|
||||
return `${PUBLIC_API_URL}${stripped}${query}`;
|
||||
}
|
||||
return `${PUBLIC_API_URL}${path}${query}`;
|
||||
}
|
||||
|
||||
async function proxyToGateway(fetchEvent: any, upstreamPath: string) {
|
||||
const req = fetchEvent.request;
|
||||
const method = req.method.toUpperCase();
|
||||
const url = new URL(req.url);
|
||||
const queryString = url.search || "";
|
||||
|
||||
// Read body for methods that have one
|
||||
let body: BodyInit | undefined;
|
||||
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
|
||||
try {
|
||||
body = await req.clone().text();
|
||||
} catch {
|
||||
body = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Forward auth + content-type + cookie
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type":
|
||||
req.headers.get("content-type") || "application/json",
|
||||
};
|
||||
const auth = req.headers.get("authorization");
|
||||
if (auth) headers["Authorization"] = auth;
|
||||
const cookie = req.headers.get("cookie");
|
||||
if (cookie) headers["Cookie"] = cookie;
|
||||
|
||||
const upstream = buildUpstream(upstreamPath, queryString);
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(upstream, {
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
cache: "no-store",
|
||||
});
|
||||
} catch (err: any) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: `Gateway unreachable: ${err?.message || "unknown"}`,
|
||||
}),
|
||||
{
|
||||
status: 502,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Copy response headers (skip hop-by-hop), ensure Content-Type
|
||||
const respHeaders = new Headers();
|
||||
response.headers.forEach((value, key) => {
|
||||
const k = key.toLowerCase();
|
||||
if (k === "server" || k === "transfer-encoding" || k === "connection") return;
|
||||
respHeaders.set(key, value);
|
||||
});
|
||||
if (!respHeaders.get("content-type")) {
|
||||
respHeaders.set("Content-Type", "application/json");
|
||||
}
|
||||
|
||||
const respBody = await response.text();
|
||||
return new Response(respBody, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: respHeaders,
|
||||
});
|
||||
}
|
||||
|
||||
export default createMiddleware({
|
||||
onRequest: async (fetchEvent) => {
|
||||
const url = new URL(fetchEvent.request.url);
|
||||
const path = url.pathname;
|
||||
|
||||
// Only handle /api/* paths
|
||||
if (!path.startsWith("/api/")) return;
|
||||
|
||||
// Gateway proxy catch-all: /api/gateway/* → strip /api/gateway, send rest
|
||||
if (path === "/api/gateway" || path.startsWith("/api/gateway/")) {
|
||||
const subPath = path.slice("/api/gateway".length) || "/";
|
||||
// Normalize to /api/... contract for the Rust gateway
|
||||
const normalized =
|
||||
subPath.startsWith("/api/") || subPath === "/api"
|
||||
? subPath
|
||||
: `/api${subPath}`;
|
||||
return proxyToGateway(fetchEvent, normalized);
|
||||
}
|
||||
|
||||
// Knowledge base routes
|
||||
if (path === "/api/kb/categories") {
|
||||
return proxyToGateway(fetchEvent, "/api/kb/categories");
|
||||
}
|
||||
if (path === "/api/kb/articles") {
|
||||
return proxyToGateway(fetchEvent, "/api/kb/articles");
|
||||
}
|
||||
if (path.startsWith("/api/kb/articles/")) {
|
||||
return proxyToGateway(fetchEvent, path);
|
||||
}
|
||||
|
||||
// Everything else under /api/* — let it fall through.
|
||||
// Returning undefined tells the framework to continue.
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
import { gatewayUrl, withAuthHeaders } from '~/lib/server/gateway';
|
||||
|
||||
/**
|
||||
* Generic gateway proxy endpoint
|
||||
* Forwards all requests to the Rust backend gateway with proper auth headers
|
||||
* Usage: /api/gateway/api/companies/jobs → forwards to gateway /api/companies/jobs
|
||||
*/
|
||||
export async function GET({ request, params }: { request: Request; params: any }) {
|
||||
return proxyRequest('GET', request, params);
|
||||
}
|
||||
|
||||
export async function POST({ request, params }: { request: Request; params: any }) {
|
||||
return proxyRequest('POST', request, params);
|
||||
}
|
||||
|
||||
export async function PUT({ request, params }: { request: Request; params: any }) {
|
||||
return proxyRequest('PUT', request, params);
|
||||
}
|
||||
|
||||
export async function DELETE({ request, params }: { request: Request; params: any }) {
|
||||
return proxyRequest('DELETE', request, params);
|
||||
}
|
||||
|
||||
export async function PATCH({ request, params }: { request: Request; params: any }) {
|
||||
return proxyRequest('PATCH', request, params);
|
||||
}
|
||||
|
||||
async function proxyRequest(method: string, request: Request, params: any) {
|
||||
try {
|
||||
// Handle different param structures
|
||||
let pathArray = params.path;
|
||||
if (!Array.isArray(pathArray)) {
|
||||
pathArray = [pathArray];
|
||||
}
|
||||
|
||||
const rawPath = `/${pathArray.join('/')}`;
|
||||
// Normalize all forwarded routes to the Rust gateway's /api/* contract.
|
||||
const path = rawPath.startsWith('/api/') || rawPath === '/api'
|
||||
? rawPath
|
||||
: `/api${rawPath}`;
|
||||
|
||||
// Preserve query string
|
||||
const url = new URL(request.url);
|
||||
const queryString = url.search ? url.search : '';
|
||||
|
||||
// Build request body if needed
|
||||
let body: string | undefined;
|
||||
if (['POST', 'PUT', 'PATCH'].includes(method)) {
|
||||
body = await request.text();
|
||||
}
|
||||
|
||||
// Forward to gateway
|
||||
const upstreamUrl = gatewayUrl(path + queryString);
|
||||
const upstreamRequest = new Request(upstreamUrl, {
|
||||
method,
|
||||
headers: withAuthHeaders(request, {
|
||||
'Content-Type': request.headers.get('Content-Type') || 'application/json',
|
||||
}),
|
||||
body,
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
const response = await fetch(upstreamRequest);
|
||||
|
||||
// Copy response headers and return
|
||||
const responseHeaders = new Headers();
|
||||
response.headers.forEach((value, key) => {
|
||||
if (!['server', 'transfer-encoding', 'connection'].includes(key.toLowerCase())) {
|
||||
responseHeaders.set(key, value);
|
||||
}
|
||||
});
|
||||
responseHeaders.set('Content-Type', 'application/json');
|
||||
|
||||
const responseBody = await response.text();
|
||||
|
||||
return new Response(responseBody, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: responseHeaders,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return new Response(
|
||||
JSON.stringify({ success: false, error: error?.message || 'Internal Server Error' }),
|
||||
{ status: 500, headers: { 'Content-Type': 'application/json' } },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import { gatewayUrl } from '~/lib/server/gateway';
|
||||
|
||||
export async function GET({ request }: { request: Request }) {
|
||||
const url = new URL(request.url);
|
||||
const upstream = gatewayUrl('/api/kb/articles' + url.search);
|
||||
try {
|
||||
const res = await fetch(upstream, { cache: 'no-store' });
|
||||
const body = await res.text();
|
||||
return new Response(body, {
|
||||
status: res.status,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
} catch (err: any) {
|
||||
return new Response(JSON.stringify({ error: err?.message || 'Gateway error' }), {
|
||||
status: 502,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import { gatewayUrl } from '~/lib/server/gateway';
|
||||
|
||||
export async function GET({ params }: { params: { slug: string } }) {
|
||||
const upstream = gatewayUrl(`/api/kb/articles/${params.slug}`);
|
||||
try {
|
||||
const res = await fetch(upstream, { cache: 'no-store' });
|
||||
const body = await res.text();
|
||||
return new Response(body, {
|
||||
status: res.status,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
} catch (err: any) {
|
||||
return new Response(JSON.stringify({ error: err?.message || 'Gateway error' }), {
|
||||
status: 502,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import { gatewayUrl } from '~/lib/server/gateway';
|
||||
|
||||
export async function GET() {
|
||||
const upstream = gatewayUrl('/api/kb/categories');
|
||||
try {
|
||||
const res = await fetch(upstream, { cache: 'no-store' });
|
||||
const body = await res.text();
|
||||
return new Response(body, {
|
||||
status: res.status,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
} catch (err: any) {
|
||||
return new Response(JSON.stringify({ error: err?.message || 'Gateway error' }), {
|
||||
status: 502,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -158,7 +158,7 @@ export default function ContactPage() {
|
|||
"Job Seeker (Apply jobs)": "GENERAL",
|
||||
};
|
||||
const category = userTypeToCategory[values().userType] || "GENERAL";
|
||||
const res = await fetch("/api/support/tickets", {
|
||||
const res = await fetch("/api/gateway/support/tickets", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
credentials: "include",
|
||||
|
|
|
|||
|
|
@ -648,7 +648,7 @@ export default function RuntimeDashboardPage() {
|
|||
const email = authEmail || getEmailFromStorage();
|
||||
if (!email) return;
|
||||
|
||||
const checkRes = await fetch("/api/auth/check-email", {
|
||||
const checkRes = await fetch("/api/gateway/auth/check-email", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
credentials: "include",
|
||||
|
|
@ -671,7 +671,7 @@ export default function RuntimeDashboardPage() {
|
|||
|
||||
const token = getToken();
|
||||
if (token) {
|
||||
const switchRes = await fetch("/api/auth/switch-role", {
|
||||
const switchRes = await fetch("/api/gateway/auth/switch-role", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export default function ForgotPasswordRoute() {
|
|||
}
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const res = await fetch('/api/auth/forgot-password', {
|
||||
const res = await fetch('/api/gateway/auth/forgot-password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: email().trim().toLowerCase() }),
|
||||
|
|
@ -82,7 +82,7 @@ export default function ForgotPasswordRoute() {
|
|||
}
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const res = await fetch('/api/auth/reset-password', {
|
||||
const res = await fetch('/api/gateway/auth/reset-password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export default function HelpCenterArticlePage() {
|
|||
(item) => (item ? fetchRelatedArticles({ article: item, limit: 4 }) : [])
|
||||
);
|
||||
const canonical = createMemo(
|
||||
() => `https://test121.nxtgauge.com/help-center/article/${encodeURIComponent(slug())}`
|
||||
() => `https://test111.nxtgauge.com/help-center/article/${encodeURIComponent(slug())}`
|
||||
);
|
||||
const pageTitle = createMemo(() => {
|
||||
const a = article();
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export default function HelpCenterPage() {
|
|||
const title = "Help Center | Nxtgauge";
|
||||
const description =
|
||||
"Browse Nxtgauge guides for getting started, roles, requests, approvals, and platform troubleshooting.";
|
||||
const canonical = "https://test121.nxtgauge.com/help-center";
|
||||
const canonical = "https://test111.nxtgauge.com/help-center";
|
||||
const [query, setQuery] = createSignal("");
|
||||
const [category, setCategory] = createSignal("");
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import PublicLanding from '~/components/PublicLanding';
|
|||
export default function Home() {
|
||||
const title = 'Nxtgauge | Verified Jobs & Professional Services Platform';
|
||||
const description = 'Trusted hiring and opportunity discovery for customers, companies, professionals, and job seekers with verification built in.';
|
||||
const canonical = 'https://test121.nxtgauge.com/';
|
||||
const canonical = 'https://test111.nxtgauge.com/';
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ export default function LoginRoute() {
|
|||
}
|
||||
setCheckingRole(true);
|
||||
try {
|
||||
const response = await fetch("/api/auth/check-email", {
|
||||
const response = await fetch("/api/gateway/auth/check-email", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
credentials: "include",
|
||||
|
|
@ -243,7 +243,7 @@ export default function LoginRoute() {
|
|||
}
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const res = await fetch("/api/auth/login", {
|
||||
const res = await fetch("/api/gateway/auth/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
credentials: "include",
|
||||
|
|
@ -283,7 +283,7 @@ export default function LoginRoute() {
|
|||
|
||||
if (discoveredRoleKeys.length === 0 || isJobSeekerRole(discoveredActiveRole)) {
|
||||
try {
|
||||
const checkRes = await fetch("/api/auth/check-email", {
|
||||
const checkRes = await fetch("/api/gateway/auth/check-email", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
credentials: "include",
|
||||
|
|
@ -324,7 +324,7 @@ export default function LoginRoute() {
|
|||
let desiredRoleKey = discoveredActiveRole;
|
||||
if (finalAccessToken && requestedRoleKey && requestedRoleKey !== discoveredActiveRole) {
|
||||
try {
|
||||
const switchRes = await fetch("/api/auth/switch-role", {
|
||||
const switchRes = await fetch("/api/gateway/auth/switch-role", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
|
@ -385,7 +385,7 @@ export default function LoginRoute() {
|
|||
setError("");
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const res = await fetch("/api/auth/resend-otp", {
|
||||
const res = await fetch("/api/gateway/auth/resend-otp", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
credentials: "include",
|
||||
|
|
@ -409,7 +409,7 @@ export default function LoginRoute() {
|
|||
}
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const verifyRes = await fetch("/api/auth/verify-email", {
|
||||
const verifyRes = await fetch("/api/gateway/auth/verify-email", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
credentials: "include",
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const chipNodes = [
|
|||
export default function ProfessionalsIndexPage() {
|
||||
const [scrollY, setScrollY] = createSignal(0);
|
||||
const [reduceMotion, setReduceMotion] = createSignal(false);
|
||||
const canonical = 'https://test121.nxtgauge.com/professionals';
|
||||
const canonical = 'https://test111.nxtgauge.com/professionals';
|
||||
const title = 'Professionals | Nxtgauge Verified Service Categories';
|
||||
const description = 'Explore verified professional categories on Nxtgauge and register with a trust-first workflow built for better opportunity quality.';
|
||||
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ export default function SignupRoute() {
|
|||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/auth/check-email", {
|
||||
const response = await fetch("/api/gateway/auth/check-email", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
credentials: "include",
|
||||
|
|
@ -218,7 +218,7 @@ export default function SignupRoute() {
|
|||
setSubmitting(true);
|
||||
try {
|
||||
console.log('[register] after canSubmit guard, calling API...');
|
||||
const res = await fetch("/api/auth/register", {
|
||||
const res = await fetch("/api/gateway/auth/register", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
credentials: "include",
|
||||
|
|
@ -311,7 +311,7 @@ export default function SignupRoute() {
|
|||
}
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const verifyRes = await fetch("/api/auth/verify-email", {
|
||||
const verifyRes = await fetch("/api/gateway/auth/verify-email", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
credentials: "include",
|
||||
|
|
@ -376,7 +376,7 @@ export default function SignupRoute() {
|
|||
setServerError("");
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const res = await fetch("/api/auth/resend-otp", {
|
||||
const res = await fetch("/api/gateway/auth/resend-otp", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
||||
credentials: "include",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
import { defineConfig } from "@solidjs/start/config";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
export default defineConfig({
|
||||
// Register our Nitro middleware that handles all /api/* paths.
|
||||
// Workaround for Vinxi 0.5.7 + @solidjs/start 1.3.2 build issue
|
||||
// where file-based API routes are not mounted as Nitro handlers.
|
||||
middleware: "./src/middleware.ts",
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
server: {
|
||||
|
|
@ -14,7 +18,7 @@ export default defineConfig({
|
|||
.replace(/^\/api\/gateway\/api(\/|$)/, "/api$1")
|
||||
.replace(/^\/api\/gateway(\/|$)/, "/api$1"),
|
||||
},
|
||||
"/api": {
|
||||
"/api/kb": {
|
||||
target: "http://localhost:9100",
|
||||
changeOrigin: true,
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue