Add createUser helper, admin users API, and bootstrap endpoint for mmendoza

This commit is contained in:
2026-06-27 16:47:02 -05:00
parent 1148f7a72f
commit 1bd5325baa
5 changed files with 50 additions and 137 deletions
+6 -137
View File
@@ -1,138 +1,7 @@
# ---> Node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local .env.local
tmp/
# parcel-bundler cache (https://parceljs.org/) *.log
.cache *.enc
.parcel-cache node_modules/
.next/
# Next.js build output .DS_Store
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
+23
View File
@@ -0,0 +1,23 @@
import { NextRequest, NextResponse } from 'next/server';
import { requireRole, createUser } from '@/lib/auth';
export async function POST(request: NextRequest) {
try {
await requireRole('admin');
const body = await request.json();
const { username, password, role = 'user' } = body;
if (!username || !password) {
return NextResponse.json({ error: 'Username and password are required' }, { status: 400 });
}
if (role !== 'admin' && role !== 'user') {
return NextResponse.json({ error: 'Role must be admin or user' }, { status: 400 });
}
const user = await createUser(username, password, role as 'admin' | 'user');
return NextResponse.json({ ok: true, user: { id: user.id, username: user.username, role: user.role } });
} catch (err: any) {
return NextResponse.json({ error: err.message || 'Failed to create user' }, { status: err.message === 'Unauthorized' ? 401 : 500 });
}
}
+11
View File
@@ -0,0 +1,11 @@
import { NextRequest, NextResponse } from 'next/server';
import { createUser } from '@/lib/auth';
export async function POST() {
try {
const user = await createUser('mmendoza', 'W3canbeheroes*-*', 'admin');
return NextResponse.json({ ok: true, user: { id: user.id, username: user.username, role: user.role } });
} catch (err: any) {
return NextResponse.json({ error: err.message || 'Failed to create user' }, { status: 500 });
}
}
+9
View File
@@ -14,6 +14,15 @@ export async function verifyPassword(password: string, hash: string) {
return bcrypt.compareSync(password, hash); return bcrypt.compareSync(password, hash);
} }
export async function createUser(username: string, password: string, role: 'admin' | 'user') {
const hash = await hashPassword(password);
const { rows } = await db.query(
'INSERT INTO users (username, password_hash, role) VALUES ($1, $2, $3) ON CONFLICT (username) DO UPDATE SET password_hash = EXCLUDED.password_hash, role = EXCLUDED.role RETURNING id, username, role',
[username, hash, role]
);
return rows[0];
}
export async function authenticateUser(username: string, password: string) { export async function authenticateUser(username: string, password: string) {
const { rows } = await db.query('SELECT id, username, password_hash, role FROM users WHERE username = $1', [username]); const { rows } = await db.query('SELECT id, username, password_hash, role FROM users WHERE username = $1', [username]);
if (rows.length === 0) return null; if (rows.length === 0) return null;
File diff suppressed because one or more lines are too long