diff --git a/app/app/api/auth/register/route.ts b/app/app/api/auth/register/route.ts
index e9efabe..5cbc79c 100644
--- a/app/app/api/auth/register/route.ts
+++ b/app/app/api/auth/register/route.ts
@@ -1,13 +1,12 @@
// app/api/auth/register/route.ts
import { NextResponse } from "next/server";
import { rateLimit, requestIp } from "@/lib/rate-limit";
+import {
+ createDirectusUser,
+ directusUserExists,
+ loginDirectus,
+} from "@/lib/directus";
-const DIRECTUS = (process.env.DIRECTUS_URL || process.env.NEXT_PUBLIC_API_BASE_URL || "").replace(/\/$/, "");
-const SERVICE_TOKEN =
-process.env.DIRECTUS_SERVICE_TOKEN ||
-process.env.DIRECTUS_ADMIN_TOKEN ||
-process.env.DIRECTUS_TOKEN_ADMIN_REGISTER || "";
-const DEFAULT_ROLE = process.env.DIRECTUS_DEFAULT_ROLE || undefined;
const SECURE = process.env.NODE_ENV === "production";
function bad(message: string, status = 400) {
@@ -15,16 +14,18 @@ function bad(message: string, status = 400) {
}
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
-async function directusLogin(email: string, password: string) {
- const r = await fetch(`${DIRECTUS}/auth/login`, {
- method: "POST",
- headers: { "Content-Type": "application/json", Accept: "application/json" },
- body: JSON.stringify({ email, password }),
- cache: "no-store",
+function isConflict(error: any): boolean {
+ const code = error?.detail?.errors?.[0]?.extensions?.code;
+ const message = error?.detail?.errors?.[0]?.message || error?.message || "";
+ return error?.status === 409 || code === "RECORD_NOT_UNIQUE" || /unique|already exists/i.test(message);
+}
+
+function logDirectusFailure(stage: string, error: any) {
+ console.error(`[auth/register] Directus ${stage} failed`, {
+ status: error?.status,
+ code: error?.detail?.errors?.[0]?.extensions?.code,
+ message: error?.detail?.errors?.[0]?.message || error?.message,
});
- const j = await r.json().catch(() => ({}));
- if (!r.ok) throw new Error(j?.errors?.[0]?.message || j?.message || `Login failed (${r.status})`);
- return j?.data || j;
}
export async function POST(req: Request) {
@@ -32,9 +33,6 @@ export async function POST(req: Request) {
if (!rateLimit(`register:${requestIp(req)}`, 5, 60 * 60_000)) {
return bad("Too many registration attempts. Please try again later.", 429);
}
- if (!DIRECTUS) return bad("Missing DIRECTUS_URL/NEXT_PUBLIC_API_BASE_URL", 500);
- if (!SERVICE_TOKEN) return bad("Missing DIRECTUS_SERVICE_TOKEN / admin token", 500);
-
const body = await req.json().catch(() => ({} as any));
const email = String(body?.email ?? "").trim().toLowerCase();
const username = String(body?.username ?? "").trim();
@@ -46,53 +44,35 @@ export async function POST(req: Request) {
if (password.length < 8) return bad("Password must be at least 8 characters");
if (password !== confirm) return bad("Passwords do not match");
- // Optional pre-check to return a friendly 409 instead of a generic Directus error
- const existsRes = await fetch(
- `${DIRECTUS}/users?filter[_or][0][email][_eq]=${encodeURIComponent(email)}` +
- `&filter[_or][1][username][_eq]=${encodeURIComponent(username)}` +
- `&fields=id,email,username&limit=1`,
- {
- headers: {
- Authorization: `Bearer ${SERVICE_TOKEN}`,
- Accept: "application/json",
- },
- cache: "no-store",
+ try {
+ if (await directusUserExists(email, username)) {
+ return bad("Email or username already in use", 409);
}
- );
- const existsJson = await existsRes.json().catch(() => ({}));
- if (Array.isArray(existsJson?.data) && existsJson.data.length > 0) {
- return bad("Email or username already in use", 409);
+ } catch (error: any) {
+ logDirectusFailure("duplicate lookup", error);
+ return bad("Sign-up is temporarily unavailable.", 503);
}
- // Create user with sane defaults
- const createPayload: any = {
- email,
- username,
- password,
- };
- if (DEFAULT_ROLE) createPayload.role = DEFAULT_ROLE;
-
- const createRes = await fetch(`${DIRECTUS}/users`, {
- method: "POST",
- headers: {
- Authorization: `Bearer ${SERVICE_TOKEN}`,
- "Content-Type": "application/json",
- Accept: "application/json",
- },
- body: JSON.stringify(createPayload),
- cache: "no-store",
- });
-
- const cj = await createRes.json().catch(() => ({}));
- if (!createRes.ok) {
- const msg = cj?.errors?.[0]?.message || cj?.message || `User create failed (${createRes.status})`;
- return bad(msg, createRes.status || 500);
+ let user: { id: string };
+ try {
+ // Resolves the production Users role through the Registration Bot
+ // policy, then creates an active local-auth account in that role.
+ user = await createDirectusUser({ email, username, password });
+ } catch (error: any) {
+ if (isConflict(error)) return bad("Email or username already in use", 409);
+ logDirectusFailure("user creation", error);
+ return bad("Sign-up is temporarily unavailable.", 503);
}
- // Auto-login (email-based; directus expects "email" even though it's an identifier)
- const tokens = await directusLogin(email, password);
+ let tokens: any;
+ try {
+ tokens = await loginDirectus(email, password);
+ } catch (error: any) {
+ logDirectusFailure("post-registration login", error);
+ return bad("Your account was created, but automatic sign-in failed. Please sign in.", 502);
+ }
- const res = NextResponse.json({ ok: true, id: cj?.data?.id || null }, { status: 201 });
+ const res = NextResponse.json({ ok: true, id: user.id }, { status: 201 });
if (tokens?.access_token) {
res.cookies.set("ma_at", tokens.access_token, {
path: "/",
diff --git a/app/app/api/bgbye/process/route.ts b/app/app/api/bgbye/process/route.ts
index 7a91181..d41da54 100644
--- a/app/app/api/bgbye/process/route.ts
+++ b/app/app/api/bgbye/process/route.ts
@@ -1,7 +1,6 @@
export const runtime = "nodejs";
import { NextResponse } from "next/server";
-import { getUserBearerFromRequest } from "@/lib/directus";
import { rateLimit, requestIp } from "@/lib/rate-limit";
const MAX_BODY_BYTES = 30 * 1024 * 1024;
@@ -21,9 +20,6 @@ export async function POST(req: Request) {
);
}
- if (!getUserBearerFromRequest(req)) {
- return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
- }
if (!rateLimit(`bgbye:${requestIp(req)}`, 10, 60_000)) {
return NextResponse.json({ error: "Too many processing requests" }, { status: 429 });
}
diff --git a/app/app/page.tsx b/app/app/page.tsx
index 739763a..cf20182 100644
--- a/app/app/page.tsx
+++ b/app/app/page.tsx
@@ -1,49 +1,15 @@
-// app/page.tsx
-import { cookies } from "next/headers";
-import { redirect } from "next/navigation";
-import SignIn from "@/app/auth/sign-in/sign-in";
-import SignUp from "@/app/auth/sign-up/sign-up";
-import { isJwtValid } from "@/lib/jwt";
+import { Suspense } from "react";
+import TemporaryUtilityHub from "@/components/TemporaryUtilityHub";
-type SearchParams = { [key: string]: string | string[] | undefined };
-
-export default async function HomePage({
- searchParams,
-}: {
- searchParams?: SearchParams;
-}) {
- // If already signed in with a VALID token, go straight to the app
- const ck = await cookies();
- const at = ck.get("ma_at")?.value;
- if (isJwtValid(at)) redirect("/portal");
-
- const reauth = searchParams?.reauth === "1";
+export const metadata = {
+ title: "Laser Everything Community Utilities",
+ description: "Free laser calculators, public files, and image background removal tools.",
+};
+export default function HomePage() {
return (
-
- {reauth && (
-
- Your session expired. Please sign in again.
-
- )}
-
-
-
MakeArmy
-
- Free to use. Manage laser rigs, settings, and projects—all in one
- place.
-
-
-
-
-
-
-
-
-
-
This is the beta build v0.1.1 - this site is an active BETA. Some features may not be available or work as intended. If you're experiencing issues please report them here: https://forge.makearmy.io/makearmy/makearmy-app/issues
-
PRIVACY: We only use cookies strictly necessary to operate the site (e.g., your sign-in session). We do not store user data or telemetry other than what you provide and never share or sell data to third parties. Ever.
+ Our database and member features are temporarily offline while we change backend
+ systems. These free utilities remain available without an account.
+
+
+ Directus was the system behind our member features, but it has moved to a corporate
+ licensing model that we cannot afford for a free community project. We are moving the
+ site to another free and open source CMS. Until that work is finished, user accounts,
+ community libraries, and databases will be unavailable. We are working to bring
+ everything back as soon as we can. Thank you for your patience.
+
diff --git a/app/components/utilities/laser-toolkit/beam-spot-size/page.tsx b/app/components/utilities/laser-toolkit/beam-spot-size/page.tsx
index 6874057..a91bb90 100644
--- a/app/components/utilities/laser-toolkit/beam-spot-size/page.tsx
+++ b/app/components/utilities/laser-toolkit/beam-spot-size/page.tsx
@@ -10,7 +10,7 @@ function num(v: string) {
return Number.isFinite(n) ? n : 0;
}
-// Spot diameter (µm) ≈ 1.27 * M² * λ(µm) * f(mm) / D(mm)
+// Focused Gaussian 1/e² diameter: d = 4 M² λ f / (π D).
export default function Page() {
const [lambdaNm, setLambdaNm] = useState("1064"); // nm (default fiber)
const [focalMm, setFocalMm] = useState("160"); // mm
@@ -21,9 +21,9 @@ export default function Page() {
const lamUm = num(lambdaNm) / 1000; // convert nm -> µm
const f = num(focalMm);
const D = num(beamDm);
- const M2 = Math.max(1, num(m2));
- if (lamUm <= 0 || f <= 0 || D <= 0) return 0;
- return 1.27 * M2 * lamUm * (f / D);
+ const M2 = num(m2);
+ if (lamUm <= 0 || f <= 0 || D <= 0 || M2 < 1) return 0;
+ return (4 / Math.PI) * M2 * lamUm * (f / D);
}, [lambdaNm, focalMm, beamDm, m2]);
const dMm = dUm / 1000;
@@ -71,7 +71,7 @@ export default function Page() {
-
Spot diameter
+
1/e² spot diameter
{dMm.toFixed(4)} mm
{dUm.toFixed(2)} µm
@@ -82,7 +82,10 @@ export default function Page() {
+
+ Gaussian-beam estimate: d = 4 M²λf/(πD), where D is the 1/e² beam diameter at the
+ lens. Real spots may be larger because of lens aberration, clipping, beam expansion, and focus error.
+
@@ -366,13 +282,12 @@ export default function Page() {
- Assumptions: Effective power includes a frequency efficiency factor η(f). Peak power uses a rectangular pulse
- approximation (shape factor ≈ 1). For real MOPA sources, pulse shape and
- true power–frequency maps vary by model; adjust fp and σ if you have vendor curves.
+ Assumptions: displayed power percentage scales average output linearly, spots are circular,
+ and peak power uses a rectangular pulse approximation. Confirm the result with a low-power test;
+ real sources can have model-specific power limits versus frequency and pulse width.
- 60–80% overlap is common for marking; deeper engraving often higher.
-
+
Pulses per spot diameter
+
{result.pulsesPerSpot.toFixed(2)}
+
+ Geometric overlap along the scan direction only. It does not predict material response;
+ pulse energy, spot profile, hatch spacing, and thermal accumulation also matter.
+