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 });
Comment on lines +10 to 13
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 | 🟠 Major

Redirect condition is inverted for SetupGuard’s required 410 behavior.

checkSetup() returns false on HTTP 410, but this guard redirects when isSetup is true, so redirect currently happens on non-410 responses.

Proposed fix
-		checkSetup()
-			.then((isSetup: boolean) => {
-				if (isSetup) {
+		checkSetup()
+			.then((isSetup: boolean) => {
+				if (!isSetup) {
 					navigate("/login", { replace: true });
 				}
 				setChecking(false);
 			})

As per coding guidelines, "SetupGuard checks /admin/auth/setup — redirects to /login on 410."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
checkSetup()
.then((isSetup: boolean) => {
if (isSetup) {
navigate("/login", { replace: true });
checkSetup()
.then((isSetup: boolean) => {
if (!isSetup) {
navigate("/login", { replace: true });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/auth/SetupGuard.tsx` around lines 10 - 13, The
redirect condition is inverted: checkSetup() returns false on HTTP 410 but the
code currently redirects when isSetup is true; update the guard in SetupGuard
(the block handling checkSetup().then(...)) to redirect to "/login" when isSetup
is falsy (i.e., change the condition to check for !isSetup) so the redirect
happens only on the 410 case; ensure you keep the navigate("/login", { replace:
true }) call and preserve surrounding promise handling in checkSetup().

}
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
11 changes: 10 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,12 @@ export const api = {
return res.blob();
},
};

export async function checkSetup(): Promise<boolean> {
const res = await fetch(`${API_BASE}/admin/auth/setup`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ _check: true }),
});
return res.status !== 410;
}
Comment on lines +98 to +105
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 | 🟠 Major

Handle setup-check statuses explicitly instead of status !== 410.

Line 104 currently maps any non-410 response (including 500/404) to true, which can incorrectly force login redirects during backend errors.

Proposed fix
 export async function checkSetup(): Promise<boolean> {
 	const res = await fetch(`${API_BASE}/admin/auth/setup`, {
 		method: "POST",
 		headers: { "Content-Type": "application/json" },
 		body: JSON.stringify({ _check: true }),
 	});
-	return res.status !== 410;
+	if (res.status === 410) return false; // setup not required anymore
+	if (res.ok) return true; // setup required
+	throw new ApiError(res.status, "Failed to verify setup status");
 }
🤖 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
currently treats any non-410 response as "setup complete"; update checkSetup to
explicitly handle expected statuses: return true only when the backend
explicitly indicates setup exists (e.g., res.status === 200), return false when
res.status === 410, and throw or surface an error for any other unexpected
status (>=400/500) so callers can handle backend errors instead of treating them
as a completed setup; update the fetch handling in checkSetup (and use API_BASE/
the same request body) to implement these explicit branches and include the
response status/text in thrown errors for debugging.

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