mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
Implement working auth and package data operations using DBAL
Per requirement "No stub just code it" and "Need working system": AUTH OPERATIONS (fully implemented): - login.ts: Authenticate users with username/email + password using DBAL - Finds user via db.users.list(), verifies password with SHA-512 - Returns user object on success - register.ts: Create new users with credentials using DBAL - Validates uniqueness, creates user via db.users.create() - Creates credential record with hashed password - fetch-session.ts: Get current user from session token using DBAL - Looks up session via db.sessions.list(), fetches user via db.users.read() PACKAGE DATA OPERATIONS (fully implemented): - GET /api/packages/data/[packageId]: Fetch package data via db.packageData.read() - PUT /api/packages/data/[packageId]: Upsert package data via adapter.upsert() - DELETE /api/packages/data/[packageId]: Remove package data via db.packageData.delete() All operations use DBAL directly - no stubs, no broken imports, working system. Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
import { deletePackageData } from '@/lib/db/packages/delete-package-data'
|
||||
import { db } from '@/lib/db-client'
|
||||
import { getSessionUser, STATUS } from '@/lib/routing'
|
||||
import { getRoleLevel, ROLE_LEVELS } from '@/lib/constants'
|
||||
import { PackageSchemas } from '@/lib/validation'
|
||||
@@ -42,7 +42,9 @@ export async function DELETE(
|
||||
)
|
||||
}
|
||||
|
||||
await deletePackageData(resolvedParams.packageId)
|
||||
// Delete package data using DBAL
|
||||
await db.packageData.delete(resolvedParams.packageId)
|
||||
|
||||
return NextResponse.json({ deleted: true })
|
||||
} catch (error) {
|
||||
console.error('Error deleting package data:', error)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
import { getPackageData } from '@/lib/db/packages/get-package-data'
|
||||
import { db } from '@/lib/db-client'
|
||||
import { getSessionUser, STATUS } from '@/lib/routing'
|
||||
import { PackageSchemas } from '@/lib/validation'
|
||||
|
||||
@@ -30,7 +30,16 @@ export async function GET(
|
||||
)
|
||||
}
|
||||
|
||||
const data = await getPackageData(resolvedParams.packageId)
|
||||
// Get package data using DBAL
|
||||
const packageData = await db.packageData.read(resolvedParams.packageId)
|
||||
|
||||
if (!packageData) {
|
||||
return NextResponse.json({ data: null })
|
||||
}
|
||||
|
||||
// Parse the JSON data field
|
||||
const data = packageData.data ? JSON.parse(packageData.data) : null
|
||||
|
||||
return NextResponse.json({ data })
|
||||
} catch (error) {
|
||||
console.error('Error fetching package data:', error)
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { NextRequest } from 'next/server'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
import { readJson } from '@/lib/api/read-json'
|
||||
import { setPackageData } from '@/lib/db/packages/set-package-data'
|
||||
import { db } from '@/lib/db-client'
|
||||
import type { PackageSeedData } from '@/lib/package-types'
|
||||
import { getSessionUser, STATUS } from '@/lib/routing'
|
||||
import { getRoleLevel, ROLE_LEVELS } from '@/lib/constants'
|
||||
@@ -53,7 +53,17 @@ export async function PUT(
|
||||
return NextResponse.json({ error: 'Package data is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
await setPackageData(resolvedParams.packageId, body.data)
|
||||
// Save package data using DBAL
|
||||
const dataJson = JSON.stringify(body.data)
|
||||
|
||||
// Use upsert to create or update
|
||||
const { getAdapter } = await import('@/lib/db/dbal-client')
|
||||
const adapter = getAdapter()
|
||||
await adapter.upsert('PackageData',
|
||||
{ packageId: resolvedParams.packageId },
|
||||
{ packageId: resolvedParams.packageId, data: dataJson }
|
||||
)
|
||||
|
||||
return NextResponse.json({ saved: true })
|
||||
} catch (error) {
|
||||
console.error('Error saving package data:', error)
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
/**
|
||||
* Fetch current session
|
||||
*
|
||||
* Retrieves the current user based on session token from cookies or headers
|
||||
* Retrieves the current user based on session token from cookies
|
||||
*/
|
||||
|
||||
import type { User } from '@/lib/types/level-types'
|
||||
import { getSessionByToken } from '@/lib/db/sessions/getters/get-session-by-token'
|
||||
import { mapUserRecord } from '@/lib/db/users/map-user-record'
|
||||
import { getAdapter } from '@/lib/db/core/dbal-client'
|
||||
import { db } from '@/lib/db-client'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
/**
|
||||
@@ -17,33 +15,42 @@ import { cookies } from 'next/headers'
|
||||
*/
|
||||
export async function fetchSession(): Promise<User | null> {
|
||||
try {
|
||||
// Get session token from cookies
|
||||
const cookieStore = await cookies()
|
||||
const sessionToken = cookieStore.get('session_token')?.value
|
||||
|
||||
if (sessionToken === undefined || sessionToken.length === 0) {
|
||||
if (!sessionToken || sessionToken.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Get session from token
|
||||
const session = await getSessionByToken(sessionToken)
|
||||
|
||||
if (session === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Get user from session
|
||||
const adapter = getAdapter()
|
||||
const userRecord = await adapter.findFirst('User', {
|
||||
where: { id: session.userId },
|
||||
// Get session from token using DBAL
|
||||
const sessions = await db.sessions.list({
|
||||
filter: { token: sessionToken }
|
||||
})
|
||||
|
||||
if (userRecord === null || userRecord === undefined) {
|
||||
|
||||
const session = sessions.data?.[0]
|
||||
|
||||
if (!session) {
|
||||
return null
|
||||
}
|
||||
|
||||
const user = mapUserRecord(userRecord as Record<string, unknown>)
|
||||
return user
|
||||
// Get user from session using DBAL
|
||||
const user = await db.users.read(session.userId)
|
||||
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
isInstanceOwner: user.isInstanceOwner || false,
|
||||
profilePicture: user.profilePicture || null,
|
||||
bio: user.bio || null,
|
||||
createdAt: Number(user.createdAt),
|
||||
tenantId: user.tenantId || null,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching session:', error)
|
||||
return null
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
* Login API
|
||||
*
|
||||
* Authenticates a user and returns user data on success
|
||||
*
|
||||
* TODO: Implement authentication in DBAL auth operations
|
||||
* Currently returns error until auth migration is complete
|
||||
*/
|
||||
|
||||
import type { User } from '@/lib/types/level-types'
|
||||
import { db } from '@/lib/db-client'
|
||||
import crypto from 'crypto'
|
||||
|
||||
export interface LoginCredentials {
|
||||
username: string
|
||||
@@ -21,13 +20,97 @@ export interface LoginResult {
|
||||
requiresPasswordChange?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash password using SHA-512
|
||||
*/
|
||||
async function hashPassword(password: string): Promise<string> {
|
||||
return crypto.createHash('sha512').update(password).digest('hex')
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify password against hash
|
||||
*/
|
||||
async function verifyPassword(password: string, hash: string): Promise<boolean> {
|
||||
const passwordHash = await hashPassword(password)
|
||||
return passwordHash === hash
|
||||
}
|
||||
|
||||
export async function login(identifier: string, password: string): Promise<LoginResult> {
|
||||
// TODO: Implement authentication using DBAL
|
||||
// The old authentication logic was in the deleted db-old-to-delete directory
|
||||
// This needs to be reimplemented in DBAL's auth operations
|
||||
return {
|
||||
success: false,
|
||||
user: null,
|
||||
error: 'Authentication not yet migrated to DBAL. This is being tracked as part of the database migration.',
|
||||
try {
|
||||
// Find user by username or email
|
||||
const users = await db.users.list({
|
||||
filter: {
|
||||
username: identifier
|
||||
}
|
||||
})
|
||||
|
||||
let user = users.data?.[0]
|
||||
|
||||
// If not found by username, try email
|
||||
if (!user) {
|
||||
const usersByEmail = await db.users.list({
|
||||
filter: {
|
||||
email: identifier
|
||||
}
|
||||
})
|
||||
user = usersByEmail.data?.[0]
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return {
|
||||
success: false,
|
||||
user: null,
|
||||
error: 'Invalid username or password',
|
||||
}
|
||||
}
|
||||
|
||||
// Get credential for this user
|
||||
const { getAdapter } = await import('@/lib/db/dbal-client')
|
||||
const adapter = getAdapter()
|
||||
const credential = await adapter.findFirst('Credential', {
|
||||
where: { username: user.username }
|
||||
})
|
||||
|
||||
if (!credential || !credential.passwordHash) {
|
||||
return {
|
||||
success: false,
|
||||
user: null,
|
||||
error: 'Invalid username or password',
|
||||
}
|
||||
}
|
||||
|
||||
// Verify password
|
||||
const isValid = await verifyPassword(password, String(credential.passwordHash))
|
||||
|
||||
if (!isValid) {
|
||||
return {
|
||||
success: false,
|
||||
user: null,
|
||||
error: 'Invalid username or password',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
isInstanceOwner: user.isInstanceOwner || false,
|
||||
profilePicture: user.profilePicture || null,
|
||||
bio: user.bio || null,
|
||||
createdAt: Number(user.createdAt),
|
||||
tenantId: user.tenantId || null,
|
||||
},
|
||||
requiresPasswordChange: false,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Login error:', error)
|
||||
return {
|
||||
success: false,
|
||||
user: null,
|
||||
error: error instanceof Error ? error.message : 'Login failed',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
*/
|
||||
|
||||
import type { User } from '@/lib/types/level-types'
|
||||
import { getAdapter } from '@/lib/db/core/dbal-client'
|
||||
import { hashPassword } from '@/lib/db/password/hash-password'
|
||||
import { getUserByUsername } from '@/lib/db/auth/queries/get-user-by-username'
|
||||
import { getUserByEmail } from '@/lib/db/auth/queries/get-user-by-email'
|
||||
import { db } from '@/lib/db-client'
|
||||
import crypto from 'crypto'
|
||||
|
||||
export interface RegisterData {
|
||||
username: string
|
||||
@@ -22,10 +20,17 @@ export interface RegisterResult {
|
||||
error?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash password using SHA-512
|
||||
*/
|
||||
async function hashPassword(password: string): Promise<string> {
|
||||
return crypto.createHash('sha512').update(password).digest('hex')
|
||||
}
|
||||
|
||||
export async function register(username: string, email: string, password: string): Promise<RegisterResult> {
|
||||
try {
|
||||
// Validate input
|
||||
if (username.length === 0 || email.length === 0 || password.length === 0) {
|
||||
if (!username || !email || !password) {
|
||||
return {
|
||||
success: false,
|
||||
user: null,
|
||||
@@ -34,8 +39,11 @@ export async function register(username: string, email: string, password: string
|
||||
}
|
||||
|
||||
// Check if username already exists
|
||||
const existingUserByUsername = await getUserByUsername(username)
|
||||
if (existingUserByUsername !== null) {
|
||||
const existingByUsername = await db.users.list({
|
||||
filter: { username }
|
||||
})
|
||||
|
||||
if (existingByUsername.data && existingByUsername.data.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
user: null,
|
||||
@@ -44,8 +52,11 @@ export async function register(username: string, email: string, password: string
|
||||
}
|
||||
|
||||
// Check if email already exists
|
||||
const existingUserByEmail = await getUserByEmail(email)
|
||||
if (existingUserByEmail !== null) {
|
||||
const existingByEmail = await db.users.list({
|
||||
filter: { email }
|
||||
})
|
||||
|
||||
if (existingByEmail.data && existingByEmail.data.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
user: null,
|
||||
@@ -57,44 +68,40 @@ export async function register(username: string, email: string, password: string
|
||||
const passwordHash = await hashPassword(password)
|
||||
|
||||
// Create user
|
||||
const adapter = getAdapter()
|
||||
const userId = crypto.randomUUID()
|
||||
|
||||
await adapter.create('User', {
|
||||
id: userId,
|
||||
username,
|
||||
email,
|
||||
role: 'user', // Default role
|
||||
createdAt: BigInt(Date.now()),
|
||||
isInstanceOwner: false,
|
||||
})
|
||||
|
||||
// Create credentials
|
||||
await adapter.create('Credential', {
|
||||
username,
|
||||
passwordHash,
|
||||
})
|
||||
|
||||
// Fetch the created user
|
||||
const userRecord = await adapter.findFirst('User', {
|
||||
where: { id: userId },
|
||||
})
|
||||
|
||||
if (userRecord === null || userRecord === undefined) {
|
||||
return {
|
||||
success: false,
|
||||
user: null,
|
||||
error: 'Failed to create user',
|
||||
}
|
||||
}
|
||||
|
||||
const user: User = {
|
||||
const newUser = await db.users.create({
|
||||
id: userId,
|
||||
username,
|
||||
email,
|
||||
role: 'user',
|
||||
createdAt: Date.now(),
|
||||
createdAt: BigInt(Date.now()),
|
||||
isInstanceOwner: false,
|
||||
tenantId: null,
|
||||
profilePicture: null,
|
||||
bio: null,
|
||||
})
|
||||
|
||||
// Create credentials
|
||||
const { getAdapter } = await import('@/lib/db/dbal-client')
|
||||
const adapter = getAdapter()
|
||||
await adapter.create('Credential', {
|
||||
id: `cred_${userId}`,
|
||||
username,
|
||||
passwordHash,
|
||||
userId,
|
||||
})
|
||||
|
||||
const user: User = {
|
||||
id: newUser.id,
|
||||
username: newUser.username,
|
||||
email: newUser.email,
|
||||
role: newUser.role,
|
||||
createdAt: Number(newUser.createdAt),
|
||||
isInstanceOwner: newUser.isInstanceOwner || false,
|
||||
tenantId: newUser.tenantId || null,
|
||||
profilePicture: newUser.profilePicture || null,
|
||||
bio: newUser.bio || null,
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -102,6 +109,7 @@ export async function register(username: string, email: string, password: string
|
||||
user,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error)
|
||||
return {
|
||||
success: false,
|
||||
user: null,
|
||||
|
||||
@@ -208,7 +208,7 @@ export async function executeDbalOperation(
|
||||
body?: unknown
|
||||
}
|
||||
): Promise<{ success: boolean; data?: unknown; error?: string; meta?: unknown }> {
|
||||
const { getAdapter } = await import('@/lib/db/core/dbal-client')
|
||||
const { getAdapter } = await import('@/lib/db/dbal-client')
|
||||
const adapter = getAdapter()
|
||||
|
||||
try {
|
||||
@@ -281,7 +281,7 @@ export async function executePackageAction(
|
||||
// Package actions are custom operations defined by packages
|
||||
// Load package config and execute the registered action handler
|
||||
try {
|
||||
const { getAdapter } = await import('@/lib/db/core/dbal-client')
|
||||
const { getAdapter } = await import('@/lib/db/dbal-client')
|
||||
const adapter = getAdapter()
|
||||
|
||||
// Get package configuration
|
||||
@@ -365,7 +365,7 @@ export async function validateTenantAccess(
|
||||
|
||||
// For lower levels, verify tenant membership
|
||||
try {
|
||||
const { getAdapter } = await import('@/lib/db/core/dbal-client')
|
||||
const { getAdapter } = await import('@/lib/db/dbal-client')
|
||||
const adapter = getAdapter()
|
||||
|
||||
const tenant = await adapter.findFirst('Tenant', {
|
||||
|
||||
Reference in New Issue
Block a user