[object Object]

← back to Norma

Fix tier_credentials schema drift in settings CRUD routes

9ba27ad9a0636fc71dee47b7a9056d807b1ee707 · 2026-07-11 08:55:30 -0700 · Steve Abrams

Drop full_name/email/is_active/last_login_at/created_by column refs that
do not exist in this deployment. SELECT/INSERT/dynamic-UPDATE/RETURNING
now match the real schema; admin-disable guard removed (no is_active).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 9ba27ad9a0636fc71dee47b7a9056d807b1ee707
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Jul 11 08:55:30 2026 -0700

    Fix tier_credentials schema drift in settings CRUD routes
    
    Drop full_name/email/is_active/last_login_at/created_by column refs that
    do not exist in this deployment. SELECT/INSERT/dynamic-UPDATE/RETURNING
    now match the real schema; admin-disable guard removed (no is_active).
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 app/api/settings/tier-credentials/[id]/route.ts | 46 ++++++-------------------
 app/api/settings/tier-credentials/route.ts      | 29 +++-------------
 2 files changed, 16 insertions(+), 59 deletions(-)

diff --git a/app/api/settings/tier-credentials/[id]/route.ts b/app/api/settings/tier-credentials/[id]/route.ts
index 7c5602c..1901940 100644
--- a/app/api/settings/tier-credentials/[id]/route.ts
+++ b/app/api/settings/tier-credentials/[id]/route.ts
@@ -9,7 +9,7 @@ interface RouteContext {
 
 /**
  * PUT /api/settings/tier-credentials/[id]
- * Update display_name, full_name, email, role, org_id, is_active. Optionally reset password.
+ * Update display_name, role, org_id. Optionally reset password.
  */
 export async function PUT(request: NextRequest, context: RouteContext) {
   const result = requireRole(request, 'admin');
@@ -19,7 +19,7 @@ export async function PUT(request: NextRequest, context: RouteContext) {
 
   try {
     const body = await request.json();
-    const { display_name, full_name, email, role, org_id, password, is_active } = body;
+    const { display_name, role, org_id, password } = body;
 
     // Verify credential exists
     const existing = await query(
@@ -37,25 +37,13 @@ export async function PUT(request: NextRequest, context: RouteContext) {
       );
     }
 
-    if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
-      return NextResponse.json({ error: 'Email is invalid' }, { status: 400 });
-    }
-
-    // Hard guard: 'admin' username cannot be disabled or demoted, otherwise the
-    // tenant can lock itself out of user management.
-    if (existing.rows[0].username === 'admin') {
-      if (is_active === false) {
-        return NextResponse.json(
-          { error: 'Cannot disable the admin account' },
-          { status: 403 },
-        );
-      }
-      if (role !== undefined && role !== 'admin') {
-        return NextResponse.json(
-          { error: 'Cannot demote the admin account' },
-          { status: 403 },
-        );
-      }
+    // Hard guard: 'admin' username cannot be demoted, otherwise the tenant can
+    // lock itself out of user management.
+    if (existing.rows[0].username === 'admin' && role !== undefined && role !== 'admin') {
+      return NextResponse.json(
+        { error: 'Cannot demote the admin account' },
+        { status: 403 },
+      );
     }
 
     // Build dynamic update
@@ -67,14 +55,6 @@ export async function PUT(request: NextRequest, context: RouteContext) {
       updates.push(`display_name = $${paramIdx++}`);
       values.push(display_name || null);
     }
-    if (full_name !== undefined) {
-      updates.push(`full_name = $${paramIdx++}`);
-      values.push(full_name || null);
-    }
-    if (email !== undefined) {
-      updates.push(`email = $${paramIdx++}`);
-      values.push(email || null);
-    }
     if (role !== undefined) {
       updates.push(`role = $${paramIdx++}`);
       values.push(role);
@@ -83,10 +63,6 @@ export async function PUT(request: NextRequest, context: RouteContext) {
       updates.push(`org_id = $${paramIdx++}`);
       values.push(org_id || null);
     }
-    if (is_active !== undefined) {
-      updates.push(`is_active = $${paramIdx++}`);
-      values.push(!!is_active);
-    }
     if (password) {
       updates.push(`password_hash = $${paramIdx++}`);
       values.push(await hashPassword(password));
@@ -99,8 +75,8 @@ export async function PUT(request: NextRequest, context: RouteContext) {
     values.push(id);
     const { rows } = await query(
       `UPDATE tier_credentials SET ${updates.join(', ')} WHERE id = $${paramIdx}
-       RETURNING id, username, role, org_id, display_name, full_name, email, is_active,
-                 last_login_at, created_at, updated_at`,
+       RETURNING id, username, role, org_id, display_name,
+                 created_at, updated_at`,
       values,
     );
 
diff --git a/app/api/settings/tier-credentials/route.ts b/app/api/settings/tier-credentials/route.ts
index 7ebee30..6704ddb 100644
--- a/app/api/settings/tier-credentials/route.ts
+++ b/app/api/settings/tier-credentials/route.ts
@@ -14,7 +14,6 @@ export async function GET(request: NextRequest) {
   try {
     const { rows } = await query(
       `SELECT tc.id, tc.username, tc.role, tc.org_id, tc.display_name,
-              tc.full_name, tc.email, tc.is_active, tc.last_login_at,
               tc.created_at, tc.updated_at,
               na.org_name
        FROM tier_credentials tc
@@ -31,7 +30,7 @@ export async function GET(request: NextRequest) {
 /**
  * POST /api/settings/tier-credentials
  * Create a new tier credential.
- * Body: { username, password, role, org_id?, display_name?, full_name?, email?, is_active? }
+ * Body: { username, password, role, org_id?, display_name? }
  */
 export async function POST(request: NextRequest) {
   const session = requireRole(request, 'admin');
@@ -45,9 +44,6 @@ export async function POST(request: NextRequest) {
       role,
       org_id,
       display_name,
-      full_name,
-      email,
-      is_active,
     } = body;
 
     if (!username || !password || !role) {
@@ -71,10 +67,6 @@ export async function POST(request: NextRequest) {
       );
     }
 
-    if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
-      return NextResponse.json({ error: 'Email is invalid' }, { status: 400 });
-    }
-
     // Check uniqueness
     const existing = await query(
       'SELECT id FROM tier_credentials WHERE username = $1',
@@ -89,29 +81,18 @@ export async function POST(request: NextRequest) {
 
     const hashed = await hashPassword(password);
 
-    // Look up creator id by username (for the created_by audit FK)
-    const creator = await query(
-      'SELECT id FROM tier_credentials WHERE username = $1',
-      [session.username]
-    );
-    const createdBy = creator.rows[0]?.id || null;
-
     const { rows } = await query(
       `INSERT INTO tier_credentials
-         (username, password_hash, role, org_id, display_name, full_name, email, is_active, created_by)
-       VALUES ($1, $2, $3, $4, $5, $6, $7, COALESCE($8, TRUE), $9)
-       RETURNING id, username, role, org_id, display_name, full_name, email, is_active,
-                 last_login_at, created_at, updated_at`,
+         (username, password_hash, role, org_id, display_name)
+       VALUES ($1, $2, $3, $4, $5)
+       RETURNING id, username, role, org_id, display_name,
+                 created_at, updated_at`,
       [
         username,
         hashed,
         role,
         org_id || null,
         display_name || null,
-        full_name || null,
-        email || null,
-        is_active === undefined ? null : !!is_active,
-        createdBy,
       ]
     );
 

← c421505 auto-save: 2026-07-11T08:42:28 (1 files) — app/api/auth/logi  ·  back to Norma  ·  Fix tier_credentials schema drift in auth + impersonate rout 86dc7d4 →