mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
Implement auth API functions (login, register, fetchSession) and enhance linter configuration
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
@@ -2,8 +2,25 @@ import js from '@eslint/js'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
/**
|
||||
* MetaBuilder ESLint Configuration
|
||||
*
|
||||
* Strict type-checking and code quality rules for the MetaBuilder platform.
|
||||
* Uses TypeScript ESLint for type-aware linting.
|
||||
*
|
||||
* Rule Categories:
|
||||
* 1. Base rules: TypeScript type-checking and code quality
|
||||
* 2. Stub file relaxations: Placeholder implementations (warnings only)
|
||||
* 3. Dynamic renderer relaxations: JSON component system (inherently dynamic)
|
||||
* 4. Test file relaxations: Test code patterns
|
||||
* 5. Type definition relaxations: Declaration files
|
||||
*/
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist', 'node_modules', 'packages/*/dist', 'packages/*/node_modules', '.next/**', 'coverage/**', 'next-env.d.ts'] },
|
||||
|
||||
// ============================================================================
|
||||
// Base Configuration - Strict Rules for Production Code
|
||||
// ============================================================================
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommendedTypeChecked],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
@@ -18,12 +35,17 @@ export default tseslint.config(
|
||||
'react-hooks': reactHooks,
|
||||
},
|
||||
rules: {
|
||||
// React Hooks
|
||||
...reactHooks.configs.recommended.rules,
|
||||
|
||||
// TypeScript Type Safety
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-unused-vars': ['error', {
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
}],
|
||||
|
||||
// Strict Boolean Expressions - Require explicit comparisons
|
||||
'@typescript-eslint/strict-boolean-expressions': ['error', {
|
||||
allowString: false,
|
||||
allowNumber: false,
|
||||
@@ -33,25 +55,48 @@ export default tseslint.config(
|
||||
allowNullableNumber: false,
|
||||
allowAny: false,
|
||||
}],
|
||||
|
||||
// Promise Handling
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/no-misused-promises': 'error',
|
||||
'@typescript-eslint/await-thenable': 'error',
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
|
||||
'@typescript-eslint/prefer-nullish-coalescing': 'warn',
|
||||
'@typescript-eslint/prefer-optional-chain': 'warn',
|
||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
||||
'@typescript-eslint/require-await': 'error',
|
||||
|
||||
// Type Assertions and Safety
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
|
||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
||||
|
||||
// Unsafe Any Operations
|
||||
'@typescript-eslint/no-unsafe-assignment': 'error',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'error',
|
||||
'@typescript-eslint/no-unsafe-call': 'error',
|
||||
'@typescript-eslint/no-unsafe-return': 'error',
|
||||
'@typescript-eslint/no-unsafe-argument': 'error',
|
||||
|
||||
// Code Style and Best Practices
|
||||
'@typescript-eslint/prefer-nullish-coalescing': 'warn',
|
||||
'@typescript-eslint/prefer-optional-chain': 'warn',
|
||||
'@typescript-eslint/no-redundant-type-constituents': 'warn',
|
||||
'@typescript-eslint/consistent-type-imports': ['warn', {
|
||||
prefer: 'type-imports',
|
||||
fixStyle: 'separate-type-imports'
|
||||
}],
|
||||
|
||||
// JavaScript Best Practices
|
||||
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
||||
'no-debugger': 'error',
|
||||
'prefer-const': 'error',
|
||||
'no-var': 'error',
|
||||
'eqeqeq': ['error', 'always', { null: 'ignore' }],
|
||||
'no-throw-literal': 'error',
|
||||
},
|
||||
},
|
||||
// Relaxed rules for stub/integration files that are placeholders
|
||||
|
||||
// ============================================================================
|
||||
// Stub/Integration Files - Relaxed Rules (Warnings)
|
||||
// ============================================================================
|
||||
// These files are placeholders for future implementation
|
||||
// Warnings allow development to continue while tracking technical debt
|
||||
{
|
||||
files: [
|
||||
'src/lib/dbal/core/client/dbal-integration/**/*.ts',
|
||||
@@ -73,7 +118,11 @@ export default tseslint.config(
|
||||
'@typescript-eslint/no-non-null-assertion': 'warn',
|
||||
},
|
||||
},
|
||||
// Relaxed rules for dynamic component renderers
|
||||
|
||||
// ============================================================================
|
||||
// Dynamic Component Renderers - Relaxed Rules
|
||||
// ============================================================================
|
||||
// JSON component system is inherently dynamic and requires some type flexibility
|
||||
{
|
||||
files: [
|
||||
'src/lib/packages/json/render-json-component.tsx',
|
||||
@@ -87,7 +136,11 @@ export default tseslint.config(
|
||||
'@typescript-eslint/no-unsafe-argument': 'warn',
|
||||
},
|
||||
},
|
||||
// Relaxed rules for test files
|
||||
|
||||
// ============================================================================
|
||||
// Test Files - Relaxed Rules
|
||||
// ============================================================================
|
||||
// Test files often need more flexibility for mocking and assertions
|
||||
{
|
||||
files: ['**/*.test.ts', '**/*.test.tsx'],
|
||||
rules: {
|
||||
@@ -95,13 +148,18 @@ export default tseslint.config(
|
||||
'@typescript-eslint/no-unsafe-argument': 'warn',
|
||||
'@typescript-eslint/no-unsafe-return': 'warn',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
},
|
||||
},
|
||||
// Relaxed rules for type definition files
|
||||
|
||||
// ============================================================================
|
||||
// Type Definition Files - Relaxed Rules
|
||||
// ============================================================================
|
||||
{
|
||||
files: ['**/*.d.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-redundant-type-constituents': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* - Entity must be declared in package schema
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import type { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
import {
|
||||
errorResponse,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Metadata } from 'next'
|
||||
import type { Metadata } from 'next'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { getAdapter } from '@/lib/db/core/dbal-client'
|
||||
import { loadJSONPackage } from '@/lib/packages/json/functions/load-json-package'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Metadata } from 'next'
|
||||
import type { Metadata } from 'next'
|
||||
import { notFound } from 'next/navigation'
|
||||
|
||||
import { UIPageRenderer } from '@/components/ui-page-renderer/UIPageRenderer'
|
||||
|
||||
@@ -87,7 +87,10 @@ describe('useAuth role mapping', () => {
|
||||
])('applies level for role "$role"', async ({ role, expectedLevel }) => {
|
||||
const { result, unmount } = renderHook(() => useAuth())
|
||||
|
||||
mockLogin.mockResolvedValue(createUser({ role }))
|
||||
mockLogin.mockResolvedValue({
|
||||
success: true,
|
||||
user: createUser({ role }),
|
||||
})
|
||||
|
||||
await waitForIdle(result)
|
||||
await act(async () => {
|
||||
|
||||
@@ -99,7 +99,10 @@ describe('useAuth session flows', () => {
|
||||
it('authenticates on login', async () => {
|
||||
const { result, unmount } = renderHook(() => useAuth())
|
||||
|
||||
mockLogin.mockResolvedValue(createUser())
|
||||
mockLogin.mockResolvedValue({
|
||||
success: true,
|
||||
user: createUser(),
|
||||
})
|
||||
|
||||
await waitForIdle(result)
|
||||
await act(async () => {
|
||||
@@ -120,7 +123,10 @@ describe('useAuth session flows', () => {
|
||||
it('clears user on logout', async () => {
|
||||
const { result, unmount } = renderHook(() => useAuth())
|
||||
|
||||
mockLogin.mockResolvedValue(createUser())
|
||||
mockLogin.mockResolvedValue({
|
||||
success: true,
|
||||
user: createUser(),
|
||||
})
|
||||
|
||||
await waitForIdle(result)
|
||||
await act(async () => {
|
||||
@@ -140,13 +146,14 @@ describe('useAuth session flows', () => {
|
||||
it('registers and authenticates', async () => {
|
||||
const { result, unmount } = renderHook(() => useAuth())
|
||||
|
||||
mockRegister.mockResolvedValue(
|
||||
createUser({
|
||||
mockRegister.mockResolvedValue({
|
||||
success: true,
|
||||
user: createUser({
|
||||
id: 'user_2',
|
||||
username: 'newbie',
|
||||
email: 'newbie@example.com',
|
||||
})
|
||||
)
|
||||
}),
|
||||
})
|
||||
|
||||
await waitForIdle(result)
|
||||
await act(async () => {
|
||||
@@ -168,7 +175,10 @@ describe('useAuth session flows', () => {
|
||||
const first = renderHook(() => useAuth())
|
||||
const second = renderHook(() => useAuth())
|
||||
|
||||
mockLogin.mockResolvedValue(createUser({ email: 'sync@example.com', username: 'sync' }))
|
||||
mockLogin.mockResolvedValue({
|
||||
success: true,
|
||||
user: createUser({ email: 'sync@example.com', username: 'sync' }),
|
||||
})
|
||||
|
||||
await waitForIdle(first.result)
|
||||
await waitForIdle(second.result)
|
||||
|
||||
@@ -51,9 +51,18 @@ export class AuthStore {
|
||||
})
|
||||
|
||||
try {
|
||||
const user = await loginRequest(identifier, password)
|
||||
const result = await loginRequest(identifier, password)
|
||||
|
||||
if (!result.success || result.user === null) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
isLoading: false,
|
||||
})
|
||||
throw new Error(result.error ?? 'Login failed')
|
||||
}
|
||||
|
||||
this.setState({
|
||||
user: mapUserToAuthUser(user),
|
||||
user: mapUserToAuthUser(result.user),
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
})
|
||||
@@ -73,9 +82,18 @@ export class AuthStore {
|
||||
})
|
||||
|
||||
try {
|
||||
const user = await registerRequest(username, email, password)
|
||||
const result = await registerRequest(username, email, password)
|
||||
|
||||
if (!result.success || result.user === null) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
isLoading: false,
|
||||
})
|
||||
throw new Error(result.error ?? 'Registration failed')
|
||||
}
|
||||
|
||||
this.setState({
|
||||
user: mapUserToAuthUser(user),
|
||||
user: mapUserToAuthUser(result.user),
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
@@ -18,7 +18,7 @@ export const mapUserToAuthUser = (user: User): AuthUser => {
|
||||
username: user.username,
|
||||
role: user.role as AuthUser['role'],
|
||||
level: getRoleLevel(user.role),
|
||||
tenantId: (user.tenantId !== null && user.tenantId.length > 0) ? user.tenantId : undefined,
|
||||
tenantId: (user.tenantId !== null && user.tenantId !== undefined && user.tenantId.length > 0) ? user.tenantId : undefined,
|
||||
profilePicture: user.profilePicture ?? undefined,
|
||||
bio: user.bio ?? undefined,
|
||||
isInstanceOwner: user.isInstanceOwner,
|
||||
|
||||
@@ -1,10 +1,51 @@
|
||||
/**
|
||||
* Fetch current session (stub)
|
||||
* Fetch current session
|
||||
*
|
||||
* Retrieves the current user based on session token from cookies or headers
|
||||
*/
|
||||
|
||||
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 { cookies } from 'next/headers'
|
||||
|
||||
export function fetchSession(): Promise<User | null> {
|
||||
// TODO: Implement session fetching
|
||||
return Promise.resolve(null)
|
||||
/**
|
||||
* Fetch the current session user
|
||||
*
|
||||
* @returns User if session is valid, null otherwise
|
||||
*/
|
||||
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 === null || sessionToken.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Get session from token
|
||||
const session = await getSessionByToken(sessionToken)
|
||||
|
||||
if (session === null || session === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Get user from session
|
||||
const adapter = getAdapter()
|
||||
const userRecord = await adapter.findFirst('User', {
|
||||
where: { id: session.userId },
|
||||
})
|
||||
|
||||
if (userRecord === null || userRecord === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
const user = mapUserRecord(userRecord as Record<string, unknown>)
|
||||
return user
|
||||
} catch (error) {
|
||||
console.error('Error fetching session:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,52 @@
|
||||
/**
|
||||
* Login API (stub)
|
||||
* Login API
|
||||
*
|
||||
* Authenticates a user and returns user data on success
|
||||
*/
|
||||
|
||||
import type { User } from '@/lib/types/level-types'
|
||||
import { authenticateUser } from '@/lib/db/auth/queries/authenticate-user'
|
||||
|
||||
export interface LoginCredentials {
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export function login(_identifier: string, _password: string): Promise<User> {
|
||||
// TODO: Implement login
|
||||
// For now, throw an error to indicate not implemented
|
||||
return Promise.reject(new Error('Login not implemented'))
|
||||
export interface LoginResult {
|
||||
success: boolean
|
||||
user: User | null
|
||||
error?: string
|
||||
requiresPasswordChange?: boolean
|
||||
}
|
||||
|
||||
export async function login(identifier: string, password: string): Promise<LoginResult> {
|
||||
try {
|
||||
const result = await authenticateUser(identifier, password)
|
||||
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
user: null,
|
||||
error: result.error === 'invalid_credentials'
|
||||
? 'Invalid username or password'
|
||||
: result.error === 'user_not_found'
|
||||
? 'User not found'
|
||||
: result.error === 'account_locked'
|
||||
? 'Account is locked'
|
||||
: 'Authentication failed',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
user: result.user,
|
||||
requiresPasswordChange: result.requiresPasswordChange,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
user: null,
|
||||
error: error instanceof Error ? error.message : 'Login failed',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
/**
|
||||
* Register API (stub)
|
||||
* Register API
|
||||
*
|
||||
* Creates a new user account with username, email, and password
|
||||
*/
|
||||
|
||||
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'
|
||||
|
||||
export interface RegisterData {
|
||||
username: string
|
||||
@@ -10,7 +16,96 @@ export interface RegisterData {
|
||||
password: string
|
||||
}
|
||||
|
||||
export function register(_username: string, _email: string, _password: string): Promise<User> {
|
||||
// TODO: Implement registration
|
||||
return Promise.reject(new Error('Registration not implemented'))
|
||||
export interface RegisterResult {
|
||||
success: boolean
|
||||
user: User | null
|
||||
error?: string
|
||||
}
|
||||
|
||||
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) {
|
||||
return {
|
||||
success: false,
|
||||
user: null,
|
||||
error: 'Username, email, and password are required',
|
||||
}
|
||||
}
|
||||
|
||||
// Check if username already exists
|
||||
const existingUserByUsername = await getUserByUsername(username)
|
||||
if (existingUserByUsername !== null) {
|
||||
return {
|
||||
success: false,
|
||||
user: null,
|
||||
error: 'Username already exists',
|
||||
}
|
||||
}
|
||||
|
||||
// Check if email already exists
|
||||
const existingUserByEmail = await getUserByEmail(email)
|
||||
if (existingUserByEmail !== null) {
|
||||
return {
|
||||
success: false,
|
||||
user: null,
|
||||
error: 'Email already exists',
|
||||
}
|
||||
}
|
||||
|
||||
// Hash password
|
||||
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 = {
|
||||
id: userId,
|
||||
username,
|
||||
email,
|
||||
role: 'user',
|
||||
createdAt: Date.now(),
|
||||
isInstanceOwner: false,
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
user,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
user: null,
|
||||
error: error instanceof Error ? error.message : 'Registration failed',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { DBALClient as _DBALClient, DBALConfig as _DBALConfig } from '@/dbal'
|
||||
import { DBALError, DBALErrorCode } from '@/dbal/core/foundation/errors'
|
||||
import type { DBALErrorCode } from '@/dbal/core/foundation/errors';
|
||||
import { DBALError } from '@/dbal/core/foundation/errors'
|
||||
|
||||
export function handleError(error: unknown): { message: string; code?: DBALErrorCode } {
|
||||
if (error instanceof DBALError) {
|
||||
|
||||
Reference in New Issue
Block a user