feat(db+auth): add pg pool, admin_users email/provider migration, refactor auth lookup

- Create src/lib/db.ts (shared pg.Pool, query/withTx helpers, server-only)
- Add @types/pg devDep
- Migration 204: add email, auth_provider, auth_subject columns to
  admin_users; backfill from auth.users; new upsert_admin_user accepts
  multi-provider args; new get_admin_user_for_session RPC resolves
  Auth.js session id (UUID or Google sub) to an admin row
- Refactor getAdminUser() to use pg + new RPC; auto-provisions on first
  Google sign-in using session.user.email
- Refactor updatePasswordAction to call update_user_password via pg
  (drops the Supabase REST hop)
- Delete orphaned src/actions/login.ts (replaced by auth-actions.ts)
- Drop remaining DEV_FORCE_UID references in users.ts; dev path now
  uses dev_session cookie (the only cookie the dev flow can set)
- Update AdminUser type: user_id is now string | null (Google users
  have no Supabase auth id); add email + auth_provider fields
- Fix downstream type errors: StopProductAssignment.callerUid accepts
  null; pickup.ts performedBy widens to string | null
- Bump vitest config, tests/, and other earlier cleanup changes
This commit is contained in:
2026-06-06 23:41:41 +00:00
parent 9374e63ae6
commit f96dcd01f2
53 changed files with 1837 additions and 1656 deletions
+8 -37
View File
@@ -1,6 +1,6 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { updatePasswordAction } from "@/actions/admin/password";
@@ -12,22 +12,6 @@ export default function ChangePasswordPage() {
const [confirm, setConfirm] = useState("");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [checkingSession, setCheckingSession] = useState(true);
const [userId, setUserId] = useState<string | null>(null);
useEffect(() => {
fetch("/api/auth/uid")
.then((r) => r.json())
.then((data) => {
if (!data.uid) {
router.push("/login");
} else {
setUserId(data.uid);
setCheckingSession(false);
}
})
.catch(() => router.push("/login"));
}, [router]);
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
@@ -51,30 +35,17 @@ export default function ChangePasswordPage() {
return;
}
if (userId) {
logUserActivity({
user_id: userId,
activity_type: "password_change",
details: {},
});
}
// Audit log (best-effort — the password change itself was the source of truth)
logUserActivity({
user_id: result.userId ?? "unknown",
activity_type: "password_change",
details: {},
}).catch(() => {});
router.push("/admin");
router.refresh();
}
if (checkingSession) {
return (
<main className="min-h-screen bg-zinc-950 px-6 py-12 flex items-start justify-center">
<div className="w-full max-w-md">
<div className="rounded-2xl bg-zinc-900 border border-zinc-800 p-8 text-center">
<p className="text-zinc-500 text-sm">Checking session...</p>
</div>
</div>
</main>
);
}
return (
<main className="min-h-screen bg-zinc-950 px-6 py-12 flex items-start justify-center">
<div className="w-full max-w-md">
@@ -147,4 +118,4 @@ export default function ChangePasswordPage() {
</div>
</main>
);
}
}