Create missing type definitions and stub implementations

- Created src/lib/types/ directory with level-types.ts and schema-types.ts
- Created missing hook implementations (use-mobile, useAutoRefresh, useCodeEditor, useDBAL, useFileTree, useGitHubFetcher, useKV)
- Created missing auth API modules (fetch-session, login, logout, register)
- Fixed import paths from relative '../../types/*' to '@/lib/*' aliases
- Updated AppConfiguration type to match Prisma schema
- Reduced TypeScript errors from 346 to 305

Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-03 22:01:48 +00:00
parent 2a7a81412d
commit 6fcba8a44f
48 changed files with 700 additions and 35 deletions

View File

@@ -0,0 +1,20 @@
/**
* Hook for detecting mobile viewport
*/
import { useState, useEffect } from 'react'
export function useIsMobile(): boolean {
const [isMobile, setIsMobile] = useState(false)
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < 768)
}
checkMobile()
window.addEventListener('resize', checkMobile)
return () => window.removeEventListener('resize', checkMobile)
}, [])
return isMobile
}

View File

@@ -0,0 +1,13 @@
/**
* Hook for auto-refreshing data at intervals
*/
import { useEffect, useCallback } from 'react'
export function useAutoRefresh(callback: () => void, interval: number = 5000): void {
const stableCallback = useCallback(callback, [callback])
useEffect(() => {
const id = setInterval(stableCallback, interval)
return () => clearInterval(id)
}, [stableCallback, interval])
}

View File

@@ -0,0 +1,55 @@
/**
* Hook for managing code editor state
*/
import { useState } from 'react'
export interface EditorFile {
path: string
content: string
language?: string
}
export interface UseCodeEditorReturn {
files: EditorFile[]
currentFile: EditorFile | null
setCurrentFile: (file: EditorFile | null) => void
updateFile: (path: string, content: string) => void
addFile: (file: EditorFile) => void
removeFile: (path: string) => void
}
export function useCodeEditor(initialFiles: EditorFile[] = []): UseCodeEditorReturn {
const [files, setFiles] = useState<EditorFile[]>(initialFiles)
const [currentFile, setCurrentFile] = useState<EditorFile | null>(
initialFiles.length > 0 ? initialFiles[0] : null
)
const updateFile = (path: string, content: string) => {
setFiles(prev =>
prev.map(f => (f.path === path ? { ...f, content } : f))
)
if (currentFile?.path === path) {
setCurrentFile({ ...currentFile, content })
}
}
const addFile = (file: EditorFile) => {
setFiles(prev => [...prev, file])
}
const removeFile = (path: string) => {
setFiles(prev => prev.filter(f => f.path !== path))
if (currentFile?.path === path) {
setCurrentFile(null)
}
}
return {
files,
currentFile,
setCurrentFile,
updateFile,
addFile,
removeFile,
}
}

View File

@@ -0,0 +1,114 @@
/**
* Hook for DBAL operations
*/
import { useState, useCallback } from 'react'
export interface UseDBALReturn {
get: (entity: string, id: string) => Promise<unknown>
list: (entity: string, filter?: Record<string, unknown>) => Promise<unknown[]>
create: (entity: string, data: Record<string, unknown>) => Promise<unknown>
update: (entity: string, id: string, data: Record<string, unknown>) => Promise<unknown>
delete: (entity: string, id: string) => Promise<void>
isLoading: boolean
error: Error | null
}
export function useDBAL(): UseDBALReturn {
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
const get = useCallback(async (entity: string, id: string) => {
setIsLoading(true)
setError(null)
try {
const response = await fetch(`/api/v1/${entity}/${id}`)
if (!response.ok) throw new Error('Failed to fetch')
return await response.json()
} catch (err) {
setError(err as Error)
throw err
} finally {
setIsLoading(false)
}
}, [])
const list = useCallback(async (entity: string, filter?: Record<string, unknown>) => {
setIsLoading(true)
setError(null)
try {
const queryString = filter ? `?${new URLSearchParams(filter as Record<string, string>).toString()}` : ''
const response = await fetch(`/api/v1/${entity}${queryString}`)
if (!response.ok) throw new Error('Failed to fetch')
return await response.json()
} catch (err) {
setError(err as Error)
throw err
} finally {
setIsLoading(false)
}
}, [])
const create = useCallback(async (entity: string, data: Record<string, unknown>) => {
setIsLoading(true)
setError(null)
try {
const response = await fetch(`/api/v1/${entity}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) throw new Error('Failed to create')
return await response.json()
} catch (err) {
setError(err as Error)
throw err
} finally {
setIsLoading(false)
}
}, [])
const update = useCallback(async (entity: string, id: string, data: Record<string, unknown>) => {
setIsLoading(true)
setError(null)
try {
const response = await fetch(`/api/v1/${entity}/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!response.ok) throw new Error('Failed to update')
return await response.json()
} catch (err) {
setError(err as Error)
throw err
} finally {
setIsLoading(false)
}
}, [])
const deleteEntity = useCallback(async (entity: string, id: string) => {
setIsLoading(true)
setError(null)
try {
const response = await fetch(`/api/v1/${entity}/${id}`, {
method: 'DELETE',
})
if (!response.ok) throw new Error('Failed to delete')
} catch (err) {
setError(err as Error)
throw err
} finally {
setIsLoading(false)
}
}, [])
return {
get,
list,
create,
update,
delete: deleteEntity,
isLoading,
error,
}
}

View File

@@ -0,0 +1,52 @@
/**
* Hook for managing file tree state
*/
import { useState } from 'react'
export interface FileNode {
path: string
name: string
type: 'file' | 'directory'
children?: FileNode[]
content?: string
}
export interface UseFileTreeReturn {
tree: FileNode[]
expandedPaths: Set<string>
selectedPath: string | null
toggleExpanded: (path: string) => void
selectPath: (path: string) => void
setTree: (tree: FileNode[]) => void
}
export function useFileTree(initialTree: FileNode[] = []): UseFileTreeReturn {
const [tree, setTree] = useState<FileNode[]>(initialTree)
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(new Set())
const [selectedPath, setSelectedPath] = useState<string | null>(null)
const toggleExpanded = (path: string) => {
setExpandedPaths(prev => {
const next = new Set(prev)
if (next.has(path)) {
next.delete(path)
} else {
next.add(path)
}
return next
})
}
const selectPath = (path: string) => {
setSelectedPath(path)
}
return {
tree,
expandedPaths,
selectedPath,
toggleExpanded,
selectPath,
setTree,
}
}

View File

@@ -0,0 +1,66 @@
/**
* Hook for fetching GitHub Actions workflow data
*/
import { useState, useEffect } from 'react'
export interface WorkflowRun {
id: number
name: string
status: string
conclusion: string | null
created_at: string
updated_at: string
html_url: string
}
export interface UseGitHubFetcherReturn {
runs: WorkflowRun[]
isLoading: boolean
error: Error | null
refresh: () => Promise<void>
}
export function useGitHubFetcher(
owner?: string,
repo?: string,
autoRefresh = false
): UseGitHubFetcherReturn {
const [runs, setRuns] = useState<WorkflowRun[]>([])
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
const fetchRuns = async () => {
if (!owner || !repo) return
setIsLoading(true)
setError(null)
try {
const response = await fetch(`/api/github/actions/runs?owner=${owner}&repo=${repo}`)
if (!response.ok) throw new Error('Failed to fetch workflow runs')
const data = await response.json()
setRuns(data.workflow_runs || [])
} catch (err) {
setError(err as Error)
} finally {
setIsLoading(false)
}
}
useEffect(() => {
fetchRuns()
}, [owner, repo])
useEffect(() => {
if (autoRefresh) {
const interval = setInterval(fetchRuns, 30000)
return () => clearInterval(interval)
}
}, [autoRefresh, owner, repo])
return {
runs,
isLoading,
error,
refresh: fetchRuns,
}
}

View File

@@ -0,0 +1,97 @@
/**
* Hook for key-value storage operations
*/
import { useState, useCallback } from 'react'
export interface UseKVReturn {
get: (key: string) => Promise<string | null>
set: (key: string, value: string) => Promise<void>
delete: (key: string) => Promise<void>
list: (prefix?: string) => Promise<string[]>
isLoading: boolean
error: Error | null
}
export function useKV(): UseKVReturn {
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
const get = useCallback(async (key: string) => {
setIsLoading(true)
setError(null)
try {
const response = await fetch(`/api/kv/${key}`)
if (!response.ok) {
if (response.status === 404) return null
throw new Error('Failed to get key')
}
const data = await response.json()
return data.value
} catch (err) {
setError(err as Error)
throw err
} finally {
setIsLoading(false)
}
}, [])
const set = useCallback(async (key: string, value: string) => {
setIsLoading(true)
setError(null)
try {
const response = await fetch(`/api/kv/${key}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ value }),
})
if (!response.ok) throw new Error('Failed to set key')
} catch (err) {
setError(err as Error)
throw err
} finally {
setIsLoading(false)
}
}, [])
const deleteKey = useCallback(async (key: string) => {
setIsLoading(true)
setError(null)
try {
const response = await fetch(`/api/kv/${key}`, {
method: 'DELETE',
})
if (!response.ok) throw new Error('Failed to delete key')
} catch (err) {
setError(err as Error)
throw err
} finally {
setIsLoading(false)
}
}, [])
const list = useCallback(async (prefix?: string) => {
setIsLoading(true)
setError(null)
try {
const queryString = prefix ? `?prefix=${encodeURIComponent(prefix)}` : ''
const response = await fetch(`/api/kv${queryString}`)
if (!response.ok) throw new Error('Failed to list keys')
const data = await response.json()
return data.keys || []
} catch (err) {
setError(err as Error)
throw err
} finally {
setIsLoading(false)
}
}, [])
return {
get,
set,
delete: deleteKey,
list,
isLoading,
error,
}
}

View File

@@ -0,0 +1,27 @@
/**
* @file fetch-session.ts
* @description Fetch current user session
*/
import type { User } from '@/lib/level-types'
export async function fetchSession(): Promise<User | null> {
try {
const response = await fetch('/api/auth/session', {
credentials: 'include',
})
if (!response.ok) {
if (response.status === 401) {
return null
}
throw new Error('Failed to fetch session')
}
const data = await response.json()
return data.user || null
} catch (error) {
console.error('Error fetching session:', error)
return null
}
}

View File

@@ -0,0 +1,25 @@
/**
* @file login.ts
* @description User login API
*/
import type { User } from '@/lib/level-types'
export async function login(identifier: string, password: string): Promise<User> {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({ identifier, password }),
})
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Login failed' }))
throw new Error(error.message || 'Login failed')
}
const data = await response.json()
return data.user
}

View File

@@ -0,0 +1,15 @@
/**
* @file logout.ts
* @description User logout API
*/
export async function logout(): Promise<void> {
const response = await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include',
})
if (!response.ok) {
throw new Error('Logout failed')
}
}

View File

@@ -0,0 +1,25 @@
/**
* @file register.ts
* @description User registration API
*/
import type { User } from '@/lib/level-types'
export async function register(username: string, email: string, password: string): Promise<User> {
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({ username, email, password }),
})
if (!response.ok) {
const error = await response.json().catch(() => ({ message: 'Registration failed' }))
throw new Error(error.message || 'Registration failed')
}
const data = await response.json()
return data.user
}

View File

@@ -1,4 +1,4 @@
import type { AppConfiguration } from '../../types/level-types'
import type { AppConfiguration } from '@/lib/level-types'
import { getAdapter } from '../core/dbal-client'
export async function getAppConfig(): Promise<AppConfiguration | null> {

View File

@@ -1,4 +1,4 @@
import type { AppConfiguration } from '../../types/level-types'
import type { AppConfiguration } from '@/lib/level-types'
import { getAdapter } from '../core/dbal-client'
export async function setAppConfig(config: AppConfiguration): Promise<void> {

View File

@@ -1,6 +1,6 @@
import { getAdapter } from '../../core/dbal-client'
import { verifyPassword } from '../../password/verify-password'
import type { User } from '../../types/level-types'
import type { User } from '@/lib/level-types'
import { getUserFirstLoginFlag } from '../../users/getters/get-user-first-login-flag'
import { mapUserRecord } from '../../users/map-user-record'

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { User } from '../../types/level-types'
import type { User } from '@/lib/level-types'
import { mapUserRecord } from '../../users/map-user-record'
/**

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { User } from '../../types/level-types'
import type { User } from '@/lib/level-types'
import { mapUserRecord } from '../../users/map-user-record'
/**

View File

@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { Comment } from '../../types/level-types'
import type { Comment } from '@/lib/level-types'
const mockCreate = vi.fn()
const mockAdapter = { create: mockCreate }

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { Comment } from '../../types/level-types'
import type { Comment } from '@/lib/level-types'
/**
* Add a single comment

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { Comment } from '../../types/level-types'
import type { Comment } from '@/lib/level-types'
type DBALCommentRecord = {
id: string

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { Comment } from '../../types/level-types'
import type { Comment } from '@/lib/level-types'
type DBALCommentRecord = {
id: string

View File

@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { Comment } from '../../types/level-types'
import type { Comment } from '@/lib/level-types'
const mockUpdate = vi.fn()
const mockAdapter = { update: mockUpdate }

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { Comment } from '../../types/level-types'
import type { Comment } from '@/lib/level-types'
/**
* Update a comment by ID

View File

@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { PageConfig } from '../../types/level-types'
import type { PageConfig } from '@/lib/level-types'
const mockCreate = vi.fn()
const mockAdapter = { create: mockCreate }

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { PageConfig } from '../../types/level-types'
import type { PageConfig } from '@/lib/level-types'
/**
* Add a page

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { PageConfig, UserRole } from '../../types/level-types'
import type { PageConfig, UserRole } from '@/lib/level-types'
type DBALPageRecord = {
id: string

View File

@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { PageConfig } from '../../types/level-types'
import type { PageConfig } from '@/lib/level-types'
const mockList = vi.fn()
const mockDelete = vi.fn()

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { PageConfig } from '../../types/level-types'
import type { PageConfig } from '@/lib/level-types'
/**
* Set all pages (replaces existing)

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { PageConfig } from '../../types/level-types'
import type { PageConfig } from '@/lib/level-types'
/**
* Update a page by ID

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { ModelSchema } from '../../types/schema-types'
import type { ModelSchema } from '@/lib/schema-types'
/**
* Update a schema by name

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { Tenant } from '../../types/level-types'
import type { Tenant } from '@/lib/level-types'
/**
* Add a new tenant

View File

@@ -1,7 +1,7 @@
import type { JsonValue } from '@/types/utility-types'
import { getAdapter } from '../../core/dbal-client'
import type { Tenant } from '../../types/level-types'
import type { Tenant } from '@/lib/level-types'
/**
* Get all tenants from database

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { Tenant } from '../../types/level-types'
import type { Tenant } from '@/lib/level-types'
/**
* Set all tenants (replaces existing)

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { Tenant } from '../../types/level-types'
import type { Tenant } from '@/lib/level-types'
/**
* Update an existing tenant

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../../core/dbal-client'
import type { User } from '../../types/level-types'
import type { User } from '@/lib/level-types'
/**
* Add a single user

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { User } from '../../types/level-types'
import type { User } from '@/lib/level-types'
/**
* Update a user by ID

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { User } from '../../types/level-types'
import type { User } from '@/lib/level-types'
import { mapUserRecord } from '../map-user-record'
/**

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { User } from '../../types/level-types'
import type { User } from '@/lib/level-types'
import { mapUserRecord } from '../map-user-record'
export type GetUsersOptions = { tenantId: string } | { scope: 'all' }

View File

@@ -1,4 +1,4 @@
import type { User } from '../../types/level-types'
import type { User } from '@/lib/level-types'
/**
* Normalize raw DB records into the shared User shape.

View File

@@ -1,4 +1,4 @@
import type { User } from '../../types/level-types'
import type { User } from '@/lib/level-types'
import { getAdapter } from '../core/dbal-client'
/**

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { User } from '../../types/level-types'
import type { User } from '@/lib/level-types'
import { mapUserRecord } from '../map-user-record'
/**

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { Workflow } from '../../types/level-types'
import type { Workflow } from '@/lib/level-types'
/**
* Add a workflow

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { Workflow } from '../../types/level-types'
import type { Workflow } from '@/lib/level-types'
type DBALWorkflowRecord = {
id: string

View File

@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { Workflow } from '../../types/level-types'
import type { Workflow } from '@/lib/level-types'
const mockList = vi.fn()
const mockDelete = vi.fn()

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { Workflow } from '../../types/level-types'
import type { Workflow } from '@/lib/level-types'
type DBALWorkflowRecord = {
id: string

View File

@@ -1,6 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { Workflow } from '../../types/level-types'
import type { Workflow } from '@/lib/level-types'
const mockUpdate = vi.fn()
const mockAdapter = { update: mockUpdate }

View File

@@ -1,5 +1,5 @@
import { getAdapter } from '../../core/dbal-client'
import type { Workflow } from '../../types/level-types'
import type { Workflow } from '@/lib/level-types'
/**
* Update a workflow by ID

View File

@@ -0,0 +1,117 @@
/**
* Core type definitions for MetaBuilder
* These types correspond to Prisma models and are used throughout the application
*/
// User role/level types
export type UserRole = 'public' | 'user' | 'moderator' | 'admin' | 'god' | 'supergod'
export interface User {
id: string
username: string
email: string
role: UserRole
profilePicture?: string
bio?: string
createdAt: number
tenantId?: string
isInstanceOwner: boolean
}
export interface Tenant {
id: string
name: string
slug: string
ownerId: string
createdAt: number
homepageConfig?: string
settings?: string
}
export interface Comment {
id: string
tenantId?: string
userId: string
content: string
createdAt: number
updatedAt?: number
parentId?: string
entityType?: string
entityId?: string
}
export interface PageConfig {
id: string
tenantId?: string
packageId?: string
path: string
title: string
description?: string
icon?: string
component?: string
componentTree: string
level: number
requiresAuth: boolean
requiredRole?: string
parentPath?: string
sortOrder: number
isPublished: boolean
}
export interface Workflow {
id: string
tenantId?: string
name: string
description?: string
trigger: string
actions: string
isActive: boolean
createdAt: number
updatedAt?: number
}
export interface LuaScript {
id: string
tenantId?: string
name: string
description?: string
script: string
isActive: boolean
createdAt: number
updatedAt?: number
}
export interface AppConfiguration {
id: string
name: string
schemas: string | unknown[] // JSON string or parsed array
workflows: string | unknown[] // JSON string or parsed array
luaScripts: string | unknown[] // JSON string or parsed array
pages: string | unknown[] // JSON string or parsed array
theme: string | unknown // JSON string or parsed object
}
export interface PowerTransferRequest {
id: string
fromUserId: string
toUserId: string
status: string
requestedAt: number
resolvedAt?: number
expiresAt?: number
}
// SMTPConfig type
export interface SMTPConfig {
id: string
tenantId?: string
host: string
port: number
username: string
password: string
fromEmail: string
fromName?: string
secure: boolean
createdAt: number
updatedAt?: number
}

View File

@@ -0,0 +1,39 @@
/**
* Schema type definitions for MetaBuilder
* These types define the structure of dynamic schemas and models
*/
export interface FieldDefinition {
name: string
type: string
label?: string
required?: boolean
defaultValue?: unknown
validation?: Record<string, unknown>
options?: Array<{ value: string; label: string }>
}
export interface ModelSchema {
id: string
tenantId?: string
name: string
label?: string
labelPlural?: string
icon?: string
fields: FieldDefinition[] | string // Can be JSON string or parsed array
listDisplay?: string[] | string
listFilter?: string[] | string
searchFields?: string[] | string
ordering?: string[] | string
validations?: Record<string, unknown> | string
hooks?: Record<string, unknown> | string
}
export interface DynamicData {
id: string
tenantId?: string
schemaId: string
data: string // JSON data
createdAt: number
updatedAt?: number
}