Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: "1.3.10"
cache: true

- name: Install dependencies
run: bun install --frozen-lockfile
Expand All @@ -36,7 +37,8 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: "1.3.10"
cache: true

- name: Install dependencies
run: bun install --frozen-lockfile
Expand All @@ -54,7 +56,8 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: "1.3.10"
cache: true

- name: Install dependencies
run: bun install --frozen-lockfile
Expand Down
8 changes: 2 additions & 6 deletions Dockerfile.project
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# ----------------------------------------------------------------------------
# Stage 1: Base
# ----------------------------------------------------------------------------
FROM oven/bun:1.3.9-debian AS base
FROM oven/bun:1.3.10-debian AS base

LABEL maintainer="Betterbase Team"
LABEL description="Betterbase Project - AI-Native Backend Platform"
Expand Down Expand Up @@ -54,14 +54,10 @@ RUN bun install --frozen-lockfile
# ----------------------------------------------------------------------------
# Stage 3: Builder
# ----------------------------------------------------------------------------
FROM base AS builder
FROM deps AS builder

WORKDIR /app

# Copy lockfile and install all dependencies
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

# Copy source code
COPY . .

Expand Down
15 changes: 4 additions & 11 deletions apps/dashboard/src/components/auth/SetupGuard.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
import { useEffect, useState } from "react";
import { useNavigate } from "react-router";
import { checkSetup } from "../../lib/api";

export function SetupGuard({ children }: { children: React.ReactNode }) {
const navigate = useNavigate();
const [checking, setChecking] = useState(true);

useEffect(() => {
// Try hitting /admin/auth/setup without a token.
// If setup is complete, login page is appropriate.
// If setup is not done, /admin/auth/setup returns 201, not 410.
fetch(`${import.meta.env.VITE_API_URL ?? "http://localhost:3001"}/admin/auth/setup`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ _check: true }), // Will fail validation but we only care about 410
})
.then((res) => {
if (res.status === 410) {
// Setup complete — redirect to login
checkSetup()
.then((isSetup: boolean) => {
if (isSetup) {
navigate("/login", { replace: true });
}
setChecking(false);
Expand Down
3 changes: 2 additions & 1 deletion apps/dashboard/src/layouts/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ const nav = [
{ label: "Projects", href: "/projects", icon: FolderOpen },
{ label: "Storage", href: "/storage", icon: HardDrive },
{ label: "Logs", href: "/logs", icon: ScrollText },
{ label: "Observability", href: "/observability", icon: BarChart2 },
{ label: "Observability", href: "/observability", icon: Activity },
{ label: "Metrics", href: "/metrics", icon: BarChart2 },
{ label: "Audit Log", href: "/audit", icon: Shield },
{ label: "Team", href: "/team", icon: Users },
{
Expand Down
7 changes: 6 additions & 1 deletion apps/dashboard/src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const API_BASE = import.meta.env.VITE_API_URL ?? "http://localhost:3001";
const API_BASE = import.meta.env.VITE_API_URL;

export class ApiError extends Error {
constructor(
Expand Down Expand Up @@ -94,3 +94,8 @@ export const api = {
return res.blob();
},
};

export async function checkSetup(): Promise<boolean> {
const res = await fetch(`${API_BASE}/admin/auth/setup-status`);
return res.status !== 410;
}
Comment on lines +98 to +101
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Root cause of setup detection bug—server validation rejects this request body.

The { _check: true } body does not satisfy the server's Zod schema ({ email: string, password: string }). This always returns a 400 validation error, never reaching the admin existence check. See related comment on SetupGuard.tsx.

Consider changing to a GET endpoint or adding server-side handling for probe requests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/lib/api.ts` around lines 98 - 105, The checkSetup function
is sending a POST with body { _check: true } which fails server Zod validation;
update checkSetup to call `${API_BASE}/admin/auth/setup` with a GET (no body, no
JSON content-type) and keep the same status check (res.status !== 410) so the
probe doesn't hit validation, or alternatively add server-side handling for
probe requests to accept this check (e.g., allow GET or a special probe route)
if you prefer server changes; locate function checkSetup in
apps/dashboard/src/lib/api.ts and remove the JSON body and POST usage when
implementing this fix.

10 changes: 8 additions & 2 deletions apps/dashboard/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

export function formatDate(date: string | Date, opts?: Intl.DateTimeFormatOptions) {
export function formatDate(
date: string | Date | null | undefined,
opts?: Intl.DateTimeFormatOptions,
) {
if (!date) return "N/A";
const d = new Date(date);
if (isNaN(d.getTime())) return "N/A";
return new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
...opts,
}).format(new Date(date));
}).format(d);
}

export function formatRelative(date: string | Date): string {
Expand Down
Loading
Loading