Compare commits

...
Sign in to create a new pull request.

25 commits

Author SHA1 Message Date
Ashwin Kumar Sivakumar
494384e6c6 fix: use kaniko for forgejo image builds
Some checks failed
build-and-push / build (push) Failing after 14s
2026-06-12 22:55:00 +05:30
Ashwin Kumar Sivakumar
46b486fb52 chore: inspect forgejo service networking
Some checks failed
build-and-push / build (push) Failing after 21s
2026-06-12 22:24:12 +05:30
Ashwin Kumar Sivakumar
e856eae414 fix: use dind service for forgejo builds
Some checks failed
build-and-push / build (push) Failing after 21s
2026-06-12 22:09:33 +05:30
Ashwin Kumar Sivakumar
b55f69ffc4 fix: install docker cli in forgejo workflow namespace
Some checks failed
build-and-push / build (push) Failing after 21s
2026-06-12 22:07:26 +05:30
Ashwin Kumar Sivakumar
fd75e7a5ea chore: retrigger after forgejo restart
Some checks failed
build-and-push / build (push) Failing after 4s
2026-06-12 22:03:38 +05:30
Ashwin Kumar Sivakumar
7a7849f5a6 chore: retrigger forgejo after main sync fix
Some checks failed
build-and-push / build (push) Failing after 4s
2026-06-12 22:00:39 +05:30
Ashwin Kumar Sivakumar
c10668aa7d chore: retrigger forgejo build after workflow sync
Some checks failed
build-and-push / build (push) Failing after 4s
2026-06-12 21:59:01 +05:30
Ashwin Kumar Sivakumar
a4cae251d9 fix: restore forgejo build workflow filename
Some checks failed
build-and-push / build (push) Failing after 29s
2026-06-12 21:54:48 +05:30
Ashwin Kumar Sivakumar
abf4095bf2 fix: use jq encoded forgejo push sync
Some checks failed
build-and-push / build (push) Failing after 4s
2026-06-12 21:45:46 +05:30
Ashwin Kumar Sivakumar
d124a91dea fix: restore working github forgejo sync 2026-06-12 21:44:07 +05:30
Ashwin Kumar Sivakumar
ef9fed75cb fix: install docker cli in forgejo workflow 2026-06-12 21:39:04 +05:30
Ashwin Kumar Sivakumar
3c437b61b3 fix: use encoded forgejo remote for sync
Some checks failed
build-and-push / build (push) Failing after 41s
2026-06-12 20:18:28 +05:30
Ashwin Kumar Sivakumar
4fc874a44b ci: add forgejo sync and build pipeline 2026-06-12 20:17:00 +05:30
Ashwin Kumar Sivakumar
4c61bcaf31 fix: remove DPR scaling from CaptchaCanvas to fix zoomed-in appearance
- Fixed canvas dimensions to 176x52 pixels (no DPR scaling)
- All drawing operations now use fixed pixel values
- Prevents captcha from appearing zoomed in on high-DPI displays
- Characters, lines, and circles are now properly positioned within bounds
2026-06-12 05:03:54 +05:30
Ashwin Kumar Sivakumar
d8467d1aeb fix: remove push preflight from forgejo mirror sync 2026-06-11 19:29:41 +05:30
Ashwin Kumar Sivakumar
cf66611750 fix: trigger forgejo mirror sync via api 2026-06-11 19:14:46 +05:30
Ashwin Kumar Sivakumar
8a8072b305 fix: use basic auth for forgejo sync 2026-06-11 18:56:39 +05:30
Ashwin Kumar Sivakumar
5d10025c34 fix: use existing forgejo mirror secrets 2026-06-11 18:19:10 +05:30
Ashwin Kumar Sivakumar
93deed0d11 fix: point forgejo sync to ashwin namespace 2026-06-11 18:00:03 +05:30
Ashwin Kumar Sivakumar
c8ecb6bf81 chore: migrate ci naming to forgejo 2026-06-11 17:17:42 +05:30
Rimuru
6666cc5f67 fix(frontend): replace broken file-based API routes with SolidStart middleware
Vinxi 0.5.7 + @solidjs/start 1.3.2 has a build bug where file-based API
routes (src/routes/api/*) are registered in the page router tree but never
mounted as Nitro handlers in the production build, so every /api/* request
returns a framework 404.

Fix: register a SolidStart middleware (src/middleware.ts) via the
middleware config field. The middleware intercepts all /api/* paths and
proxies them to the Rust gateway, bypassing the broken page router.

Covers:
- /api/gateway/* (catch-all proxy to gateway)
- /api/kb/categories
- /api/kb/articles
- /api/kb/articles/:slug

Also tightens the dev-server vite proxy from /api to /api/kb so it
doesn't shadow the new middleware in dev.

Removes the dead src/routes/api/ tree (no longer used).
2026-06-11 15:36:44 +05:30
Ashwin Kumar Sivakumar
aabfacc735 fix: route client /api/* through /api/gateway/* proxy\n\nLogin, signup, forgot-password, dashboard, contact, help-center, and all dashboard component fetches were calling bare /api/* paths. The SolidStart server has no /api/auth/*, /api/support/*, or /api/kb/* handlers -- only /api/gateway/[...path] proxies to the Rust gateway. So those calls returned HTML (SPA catch-all), the JSON parse threw silently, and the buttons looked dead. Sign In / Sign Up / Forgot Password all appeared to be no-ops.\n\nThis routes every client-side fetch through the existing gateway proxy, matching the pattern already used by src/lib/api.ts.\n\nAlso fixes hardcoded test121 -> test111 in canonical/og:url tags across index, professionals, help-center, and RoleLandingPage. 2026-06-11 14:10:10 +05:30
Ashwin Kumar Sivakumar
31101db955 chore: rebuild trigger #2 2026-06-08 21:33:46 +05:30
Ashwin Kumar Sivakumar
4930a24e35 chore: trigger rebuild with base image now available 2026-06-08 21:16:18 +05:30
Ashwin Kumar Sivakumar
b21c3e43b1 chore: trigger build 2026-06-08 21:06:25 +05:30
32 changed files with 385 additions and 328 deletions

View 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}"

View file

@ -11,7 +11,7 @@ Usage:
This script: This script:
1. Updates the newTag for the specified service to the SHA 1. Updates the newTag for the specified service to the SHA
2. Commits and pushes to the gitops repo 2. Commits and pushes to the gitops repo
3. ArgoCD detects the change and deploys 3. Flux detects the change and deploys
""" """
import argparse import argparse
@ -98,10 +98,14 @@ def main():
image_name = f"nxtgauge-{args.service}" image_name = f"nxtgauge-{args.service}"
# Find the right kustomization file based on 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") kustomization_path = os.path.join(args.repo, "apps/nxtgauge-frontend-solid/overlays/prod/kustomization.yaml")
if not os.path.exists(kustomization_path): if not os.path.exists(kustomization_path):
kustomization_path = os.path.join(args.repo, "apps/nxtgauge-frontend-solid/base/kustomization.yaml") 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: elif "ai-assistant" in args.service:
kustomization_path = os.path.join(args.repo, "apps/nxtgauge-ai-assistant/overlays/prod/kustomization.yaml") kustomization_path = os.path.join(args.repo, "apps/nxtgauge-ai-assistant/overlays/prod/kustomization.yaml")
if not os.path.exists(kustomization_path): if not os.path.exists(kustomization_path):

View 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"

View file

@ -1,4 +1,4 @@
name: build-and-push name: Build Frontend And Update GitOps
on: on:
push: push:
@ -15,97 +15,57 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Docker Buildx - name: Install Docker CLI
run: | run: |
export DOCKER_HOST=unix:///var/run/docker.sock apt-get update
docker version apt-get install -y docker.io
docker buildx create --use || true
docker buildx inspect --bootstrap
- name: Login to Registry - name: Log in to registry
env: run: |
REGISTRY_HOSTPORT: ${{ secrets.REGISTRY_HOSTPORT }} echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.nxtgauge.com -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} - name: Build and push frontend image
run: | run: |
set -euo pipefail set -euo pipefail
export DOCKER_HOST=unix:///var/run/docker.sock IMAGE="registry.nxtgauge.com/nxtgauge-frontend-solid:${{ github.sha }}"
test -n "$REGISTRY_HOSTPORT" docker build -t "${IMAGE}" -t registry.nxtgauge.com/nxtgauge-frontend-solid:latest .
echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_HOSTPORT" -u "$REGISTRY_USERNAME" --password-stdin 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: 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: | run: |
set -euo pipefail set -euo pipefail
export DOCKER_HOST=unix:///var/run/docker.sock test -n "${GITOPS_PASSWORD:-}" || { echo "GITOPS_PASSWORD is empty"; exit 1; }
AUTH="$(printf '%s' "${GITOPS_USERNAME}:${GITOPS_PASSWORD}" | base64 -w0)"
build_and_push() { TMP_DIR="$(mktemp -d)"
docker buildx build --push \ git -c http.extraHeader="AUTHORIZATION: basic ${AUTH}" clone --branch main "${GITOPS_REPO}" "${TMP_DIR}"
-f Dockerfile \ cd "${TMP_DIR}"
-t "$REGISTRY_HOSTPORT/nxtgauge-frontend-solid:${{ gitea.sha }}" \ python3 - <<'PY'
-t "$REGISTRY_HOSTPORT/nxtgauge-frontend-solid:high-performance-latest" \ from pathlib import Path
. import os
} path = Path('apps/nxtgauge-frontend-solid/overlays/prod/kustomization.yaml')
lines = path.read_text().splitlines()
for attempt in 1 2 3; do out = []
echo "Build attempt $attempt" for line in lines:
if build_and_push; then if line.strip().startswith('newTag:'):
exit 0 indent = line[:len(line) - len(line.lstrip())]
fi out.append(f"{indent}newTag: {os.environ['IMAGE_TAG']}")
echo "Build attempt $attempt failed; recreating builder and retrying" else:
docker buildx rm --all-inactive --force || true out.append(line)
docker buildx create --use || true path.write_text('\n'.join(out) + '\n')
docker buildx inspect --bootstrap PY
sleep $((attempt * 10)) git config user.name "forgejo-actions"
done git config user.email "forgejo-actions@nxtgauge.com"
git add apps/nxtgauge-frontend-solid/overlays/prod/kustomization.yaml
echo "Build failed after retries" git diff --cached --quiet && exit 0
exit 1 git commit -m "chore(gitops): update frontend image to ${IMAGE_TAG}"
git -c http.extraHeader="AUTHORIZATION: basic ${AUTH}" push origin main
- 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"

40
.github/workflows/sync-to-forgejo.yml vendored Normal file
View 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

View file

@ -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

View file

@ -12,3 +12,6 @@ See `docs/MIGRATION_MASTER_PLAN.md` for the staged plan.
Required secrets: Required secrets:
- `REGISTRY_USERNAME` - `REGISTRY_USERNAME`
- `REGISTRY_PASSWORD` - `REGISTRY_PASSWORD`
# Mon Jun 8 09:06:25 PM IST 2026
# Mon Jun 8 09:16:18 PM IST 2026

View file

@ -102,9 +102,9 @@ Visual tests compare screenshots against baselines in `tests/e2e/visual/`.
## CI / Nightly Runs ## 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 - **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: Artifacts are uploaded:
- `vitest-coverage/` — coverage reports - `vitest-coverage/` — coverage reports

View file

@ -21,53 +21,52 @@ export default function CaptchaCanvas(props: CaptchaCanvasProps) {
const width = 176; const width = 176;
const height = 52; const height = 52;
const dpr = typeof window !== 'undefined' ? Math.max(1, window.devicePixelRatio || 1) : 1;
// Set canvas resolution first (before any drawing) // Set canvas resolution (fixed at 1x to prevent zoom issues)
canvas.width = Math.floor(width * dpr); canvas.width = width;
canvas.height = Math.floor(height * dpr); canvas.height = height;
canvas.style.width = `${width}px`; canvas.style.width = `${width}px`;
canvas.style.height = `${height}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 // Clear and fill background
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, width, height);
ctx.fillStyle = '#ffffff'; 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) { 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.strokeStyle = i % 2 === 0 ? 'rgba(253,98,22,0.16)' : 'rgba(27,36,64,0.14)';
ctx.lineWidth = 1; ctx.lineWidth = 1;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(Math.random() * canvas.width, Math.random() * canvas.height); ctx.moveTo(Math.random() * width, Math.random() * height);
ctx.lineTo(Math.random() * canvas.width, Math.random() * canvas.height); ctx.lineTo(Math.random() * width, Math.random() * height);
ctx.stroke(); ctx.stroke();
} }
// Draw decorative circles // Draw decorative circles (within bounds)
for (let i = 0; i < 3; i += 1) { 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.fillStyle = i % 2 === 0 ? 'rgba(253,98,22,0.10)' : 'rgba(27,36,64,0.09)';
ctx.beginPath(); 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(); ctx.fill();
} }
// Draw characters // Draw characters (fixed positioning)
const chars = String(props.code || '').slice(0, 6).split(''); const chars = String(props.code || '').slice(0, 6).split('');
const startX = 16 * dpr; const startX = 16;
const charGap = 24 * dpr; const charGap = 24;
chars.forEach((char, index) => { chars.forEach((char, index) => {
const x = startX + index * charGap; const x = startX + index * charGap;
const y = canvas.height / 2; const y = height / 2;
const rotation = 0; const rotation = 0;
ctx.save(); ctx.save();
ctx.translate(x, y); ctx.translate(x, y);
ctx.rotate(rotation); ctx.rotate(rotation);
ctx.textBaseline = 'middle'; 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.fillStyle = index % 2 === 0 ? '#0f172a' : '#c2410c';
ctx.lineWidth = 0; ctx.lineWidth = 0;
ctx.fillText(char, 0, 0); ctx.fillText(char, 0, 0);

View file

@ -98,7 +98,7 @@ export default function DashboardLayout(props: ParentProps) {
const token = sessionStorage.getItem("nxtgauge_access_token"); const token = sessionStorage.getItem("nxtgauge_access_token");
if (token) { if (token) {
try { try {
const res = await fetch("/api/auth/session", { const res = await fetch("/api/gateway/auth/session", {
headers: { headers: {
Accept: "application/json", Accept: "application/json",
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,

View file

@ -115,7 +115,7 @@ export default function RoleLandingPage(props: Props) {
return item; 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 pageTitle = createMemo(() => (content() ? `${content()!.shortTitle} | Nxtgauge` : 'Role | Nxtgauge'));
const pageDescription = createMemo(() => const pageDescription = createMemo(() =>
content() ? `${content()!.heroDescription} Most role reviews complete in 24-48 hours.` : 'Role landing page on Nxtgauge.' content() ? `${content()!.heroDescription} Most role reviews complete in 24-48 hours.` : 'Role landing page on Nxtgauge.'

View file

@ -1054,7 +1054,7 @@ export default function DashboardDesignPreview(props: {
try { try {
const token = getToken(); const token = getToken();
if (!token) return; 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}` }, headers: { Authorization: `Bearer ${token}` },
credentials: 'include', credentials: 'include',
}); });

View file

@ -267,14 +267,14 @@ export default function MyDashboardPage(props: Props) {
try { try {
if (roleKey === 'COMPANY') { if (roleKey === 'COMPANY') {
const [jobsRes, appsRes] = await Promise.all([ 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', credentials: 'include',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authorization: `Bearer ${window.sessionStorage.getItem('nxtgauge_access_token') || ''}`, 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', credentials: 'include',
headers: { headers: {
'Content-Type': 'application/json', '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.'); if (!jobsRes.ok && !appsRes.ok) setErr('Some company metrics could not be loaded.');
} else if (roleKey === 'CUSTOMER') { } 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', credentials: 'include',
headers: { headers: {
'Content-Type': 'application/json', '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.'); if (!res.ok) setErr('Some customer metrics could not be loaded.');
} else if (roleKey === 'JOB_SEEKER') { } else if (roleKey === 'JOB_SEEKER') {
const [jobsRes, appsRes] = await Promise.all([ 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', credentials: 'include',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Authorization: `Bearer ${window.sessionStorage.getItem('nxtgauge_access_token') || ''}`, 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', credentials: 'include',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View file

@ -82,7 +82,7 @@ async function fetchSession(): Promise<AuthUser | null> {
const token = getToken(); const token = getToken();
if (!token) return null; if (!token) return null;
try { try {
const res = await fetch("/api/auth/session", { const res = await fetch("/api/gateway/auth/session", {
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,

View file

@ -42,7 +42,7 @@ export async function fetchHelpCenterArticles(input: {
// Fallback: when backend search returns sparse/empty data, apply local filtering // Fallback: when backend search returns sparse/empty data, apply local filtering
// so users can still find articles by simple keywords. // so users can still find articles by simple keywords.
if (input.q && items.length === 0) { if (input.q && items.length === 0) {
const allRes = await fetch("/api/kb/articles"); const allRes = await fetch("/api/gateway/kb/articles");
if (allRes.ok) { if (allRes.ok) {
const allData = await allRes.json(); const allData = await allRes.json();
const allRaw: any[] = Array.isArray(allData) ? allData : (allData.articles ?? []); const allRaw: any[] = Array.isArray(allData) ? allData : (allData.articles ?? []);
@ -62,7 +62,7 @@ export async function fetchHelpCenterArticles(input: {
export async function fetchHelpCenterCategories(): Promise<HelpCategory[]> { export async function fetchHelpCenterCategories(): Promise<HelpCategory[]> {
try { try {
const res = await fetch("/api/kb/categories"); const res = await fetch("/api/gateway/kb/categories");
if (!res.ok) return HELP_CENTER_SEED_CATEGORIES; if (!res.ok) return HELP_CENTER_SEED_CATEGORIES;
const data = await res.json(); const data = await res.json();
const raw: any[] = Array.isArray(data) ? data : (data.categories ?? []); const raw: any[] = Array.isArray(data) ? data : (data.categories ?? []);
@ -98,7 +98,7 @@ export async function fetchRelatedArticles(input: {
limit?: number; limit?: number;
}): Promise<HelpArticle[]> { }): Promise<HelpArticle[]> {
try { try {
const res = await fetch("/api/kb/articles"); const res = await fetch("/api/gateway/kb/articles");
if (!res.ok) if (!res.ok)
return pickRelated( return pickRelated(
HELP_CENTER_SEED_ARTICLES as HelpArticle[], HELP_CENTER_SEED_ARTICLES as HelpArticle[],

147
src/middleware.ts Normal file
View 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;
},
});

View file

@ -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' } },
);
}
}

View file

@ -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' },
});
}
}

View file

@ -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' },
});
}
}

View file

@ -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' },
});
}
}

View file

@ -158,7 +158,7 @@ export default function ContactPage() {
"Job Seeker (Apply jobs)": "GENERAL", "Job Seeker (Apply jobs)": "GENERAL",
}; };
const category = userTypeToCategory[values().userType] || "GENERAL"; const category = userTypeToCategory[values().userType] || "GENERAL";
const res = await fetch("/api/support/tickets", { const res = await fetch("/api/gateway/support/tickets", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" }, headers: { "Content-Type": "application/json", Accept: "application/json" },
credentials: "include", credentials: "include",

View file

@ -648,7 +648,7 @@ export default function RuntimeDashboardPage() {
const email = authEmail || getEmailFromStorage(); const email = authEmail || getEmailFromStorage();
if (!email) return; if (!email) return;
const checkRes = await fetch("/api/auth/check-email", { const checkRes = await fetch("/api/gateway/auth/check-email", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" }, headers: { "Content-Type": "application/json", Accept: "application/json" },
credentials: "include", credentials: "include",
@ -671,7 +671,7 @@ export default function RuntimeDashboardPage() {
const token = getToken(); const token = getToken();
if (token) { if (token) {
const switchRes = await fetch("/api/auth/switch-role", { const switchRes = await fetch("/api/gateway/auth/switch-role", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",

View file

@ -48,7 +48,7 @@ export default function ForgotPasswordRoute() {
} }
setSubmitting(true); setSubmitting(true);
try { try {
const res = await fetch('/api/auth/forgot-password', { const res = await fetch('/api/gateway/auth/forgot-password', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email().trim().toLowerCase() }), body: JSON.stringify({ email: email().trim().toLowerCase() }),
@ -82,7 +82,7 @@ export default function ForgotPasswordRoute() {
} }
setSubmitting(true); setSubmitting(true);
try { try {
const res = await fetch('/api/auth/reset-password', { const res = await fetch('/api/gateway/auth/reset-password', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({

View file

@ -26,7 +26,7 @@ export default function HelpCenterArticlePage() {
(item) => (item ? fetchRelatedArticles({ article: item, limit: 4 }) : []) (item) => (item ? fetchRelatedArticles({ article: item, limit: 4 }) : [])
); );
const canonical = createMemo( 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 pageTitle = createMemo(() => {
const a = article(); const a = article();

View file

@ -10,7 +10,7 @@ export default function HelpCenterPage() {
const title = "Help Center | Nxtgauge"; const title = "Help Center | Nxtgauge";
const description = const description =
"Browse Nxtgauge guides for getting started, roles, requests, approvals, and platform troubleshooting."; "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 [query, setQuery] = createSignal("");
const [category, setCategory] = createSignal(""); const [category, setCategory] = createSignal("");

View file

@ -4,7 +4,7 @@ import PublicLanding from '~/components/PublicLanding';
export default function Home() { export default function Home() {
const title = 'Nxtgauge | Verified Jobs & Professional Services Platform'; 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 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 ( return (
<> <>

View file

@ -133,7 +133,7 @@ export default function LoginRoute() {
} }
setCheckingRole(true); setCheckingRole(true);
try { try {
const response = await fetch("/api/auth/check-email", { const response = await fetch("/api/gateway/auth/check-email", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" }, headers: { "Content-Type": "application/json", Accept: "application/json" },
credentials: "include", credentials: "include",
@ -243,7 +243,7 @@ export default function LoginRoute() {
} }
setSubmitting(true); setSubmitting(true);
try { try {
const res = await fetch("/api/auth/login", { const res = await fetch("/api/gateway/auth/login", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" }, headers: { "Content-Type": "application/json", Accept: "application/json" },
credentials: "include", credentials: "include",
@ -283,7 +283,7 @@ export default function LoginRoute() {
if (discoveredRoleKeys.length === 0 || isJobSeekerRole(discoveredActiveRole)) { if (discoveredRoleKeys.length === 0 || isJobSeekerRole(discoveredActiveRole)) {
try { try {
const checkRes = await fetch("/api/auth/check-email", { const checkRes = await fetch("/api/gateway/auth/check-email", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" }, headers: { "Content-Type": "application/json", Accept: "application/json" },
credentials: "include", credentials: "include",
@ -324,7 +324,7 @@ export default function LoginRoute() {
let desiredRoleKey = discoveredActiveRole; let desiredRoleKey = discoveredActiveRole;
if (finalAccessToken && requestedRoleKey && requestedRoleKey !== discoveredActiveRole) { if (finalAccessToken && requestedRoleKey && requestedRoleKey !== discoveredActiveRole) {
try { try {
const switchRes = await fetch("/api/auth/switch-role", { const switchRes = await fetch("/api/gateway/auth/switch-role", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -385,7 +385,7 @@ export default function LoginRoute() {
setError(""); setError("");
setSubmitting(true); setSubmitting(true);
try { try {
const res = await fetch("/api/auth/resend-otp", { const res = await fetch("/api/gateway/auth/resend-otp", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" }, headers: { "Content-Type": "application/json", Accept: "application/json" },
credentials: "include", credentials: "include",
@ -409,7 +409,7 @@ export default function LoginRoute() {
} }
setSubmitting(true); setSubmitting(true);
try { try {
const verifyRes = await fetch("/api/auth/verify-email", { const verifyRes = await fetch("/api/gateway/auth/verify-email", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" }, headers: { "Content-Type": "application/json", Accept: "application/json" },
credentials: "include", credentials: "include",

View file

@ -17,7 +17,7 @@ const chipNodes = [
export default function ProfessionalsIndexPage() { export default function ProfessionalsIndexPage() {
const [scrollY, setScrollY] = createSignal(0); const [scrollY, setScrollY] = createSignal(0);
const [reduceMotion, setReduceMotion] = createSignal(false); 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 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.'; const description = 'Explore verified professional categories on Nxtgauge and register with a trust-first workflow built for better opportunity quality.';

View file

@ -133,7 +133,7 @@ export default function SignupRoute() {
} }
try { try {
const response = await fetch("/api/auth/check-email", { const response = await fetch("/api/gateway/auth/check-email", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" }, headers: { "Content-Type": "application/json", Accept: "application/json" },
credentials: "include", credentials: "include",
@ -218,7 +218,7 @@ export default function SignupRoute() {
setSubmitting(true); setSubmitting(true);
try { try {
console.log('[register] after canSubmit guard, calling API...'); 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", method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" }, headers: { "Content-Type": "application/json", Accept: "application/json" },
credentials: "include", credentials: "include",
@ -311,7 +311,7 @@ export default function SignupRoute() {
} }
setSubmitting(true); setSubmitting(true);
try { try {
const verifyRes = await fetch("/api/auth/verify-email", { const verifyRes = await fetch("/api/gateway/auth/verify-email", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" }, headers: { "Content-Type": "application/json", Accept: "application/json" },
credentials: "include", credentials: "include",
@ -376,7 +376,7 @@ export default function SignupRoute() {
setServerError(""); setServerError("");
setSubmitting(true); setSubmitting(true);
try { try {
const res = await fetch("/api/auth/resend-otp", { const res = await fetch("/api/gateway/auth/resend-otp", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" }, headers: { "Content-Type": "application/json", Accept: "application/json" },
credentials: "include", credentials: "include",

View file

@ -2,6 +2,10 @@
import { defineConfig } from "@solidjs/start/config"; import { defineConfig } from "@solidjs/start/config";
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from "@tailwindcss/vite";
export default defineConfig({ 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: { vite: {
plugins: [tailwindcss()], plugins: [tailwindcss()],
server: { server: {
@ -14,7 +18,7 @@ export default defineConfig({
.replace(/^\/api\/gateway\/api(\/|$)/, "/api$1") .replace(/^\/api\/gateway\/api(\/|$)/, "/api$1")
.replace(/^\/api\/gateway(\/|$)/, "/api$1"), .replace(/^\/api\/gateway(\/|$)/, "/api$1"),
}, },
"/api": { "/api/kb": {
target: "http://localhost:9100", target: "http://localhost:9100",
changeOrigin: true, changeOrigin: true,
}, },