← 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
M app/api/settings/tier-credentials/[id]/route.tsM app/api/settings/tier-credentials/route.ts
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 →