Add self-hosted auth, admin API, and owner web dashboard

- Replace Clerk with self-hosted JWT auth (register/login/verify, bcrypt
  passwords, Gmail OTP with console fallback)
- Add lib/db.ts pg pool + transaction helpers; seed script migrates legacy
  Clerk-era schema (drop clerk_id, enforce UUID ids and unique email)
- Add owner-gated admin API: stats, users, drivers CRUD, rides
- Add dashboard/ Vite React owner dashboard (login, overview, users,
  fleet, rides) with dev-server proxy to avoid Expo CORS middleware
- Add scripts/set-owner.mjs for role management
This commit is contained in:
Krikorios
2026-08-23 16:38:41 +03:00
parent fbe92c9d16
commit a0b297285a
75 changed files with 5158 additions and 2837 deletions
+14 -53
View File
@@ -1,16 +1,13 @@
import { neon } from "@neondatabase/serverless";
import { requireAuth } from "@/lib/jwt";
import { sql } from "@/lib/db";
export async function GET(req: Request) {
const sql = neon(process.env.DATABASE_URL!);
const clerkId = new URL(req.url).searchParams.get("clerkId");
if (!clerkId) {
return Response.json({ error: "Missing clerkId" }, { status: 400 });
}
const auth = requireAuth(req);
if ("error" in auth) return auth.error;
try {
const response = await sql`
SELECT id, name, email, role FROM users WHERE clerk_id = ${clerkId}
SELECT id, name, email, phone, role FROM users WHERE id = ${auth.userId}
`;
return Response.json({ data: response[0] ?? null });
@@ -21,57 +18,21 @@ export async function GET(req: Request) {
}
}
export async function POST(req: Request) {
const sql = neon(process.env.DATABASE_URL!);
const { name, email, clerkId } = await req.json();
if (!name || !email || !clerkId) {
return Response.json(
{
error: "Missing required fields!",
},
{
status: 404,
},
);
}
try {
const response = await sql`
INSERT INTO users (
name,
email,
clerk_id
)
VALUES (
${name},
${email},
${clerkId}
)
`;
return new Response(JSON.stringify({ data: response }));
} catch (error) {
console.log(error);
return Response.json({ error }, { status: 500 });
}
}
export async function PATCH(req: Request) {
const sql = neon(process.env.DATABASE_URL!);
const { clerkId, role } = await req.json();
const auth = requireAuth(req);
if ("error" in auth) return auth.error;
if (!clerkId || !["rider", "driver"].includes(role)) {
return Response.json(
{ error: "Missing clerkId or invalid role." },
{ status: 400 },
);
const { role } = await req.json();
if (!["rider", "driver"].includes(role)) {
return Response.json({ error: "Invalid role." }, { status: 400 });
}
try {
const response = await sql`
UPDATE users SET role = ${role} WHERE clerk_id = ${clerkId} RETURNING id, role
UPDATE users SET role = ${role}
WHERE id = ${auth.userId}
RETURNING id, role
`;
if (response.length === 0) {