diff --git a/frontends/nextjs/src/hooks/use-mobile.ts b/frontends/nextjs/src/hooks/use-mobile.ts new file mode 100644 index 000000000..0a2ef8595 --- /dev/null +++ b/frontends/nextjs/src/hooks/use-mobile.ts @@ -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 +} diff --git a/frontends/nextjs/src/hooks/useAutoRefresh.ts b/frontends/nextjs/src/hooks/useAutoRefresh.ts new file mode 100644 index 000000000..c769ba22c --- /dev/null +++ b/frontends/nextjs/src/hooks/useAutoRefresh.ts @@ -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]) +} diff --git a/frontends/nextjs/src/hooks/useCodeEditor.ts b/frontends/nextjs/src/hooks/useCodeEditor.ts new file mode 100644 index 000000000..4586fbd2f --- /dev/null +++ b/frontends/nextjs/src/hooks/useCodeEditor.ts @@ -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(initialFiles) + const [currentFile, setCurrentFile] = useState( + 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, + } +} diff --git a/frontends/nextjs/src/hooks/useDBAL.ts b/frontends/nextjs/src/hooks/useDBAL.ts new file mode 100644 index 000000000..079a70711 --- /dev/null +++ b/frontends/nextjs/src/hooks/useDBAL.ts @@ -0,0 +1,114 @@ +/** + * Hook for DBAL operations + */ +import { useState, useCallback } from 'react' + +export interface UseDBALReturn { + get: (entity: string, id: string) => Promise + list: (entity: string, filter?: Record) => Promise + create: (entity: string, data: Record) => Promise + update: (entity: string, id: string, data: Record) => Promise + delete: (entity: string, id: string) => Promise + isLoading: boolean + error: Error | null +} + +export function useDBAL(): UseDBALReturn { + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(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) => { + setIsLoading(true) + setError(null) + try { + const queryString = filter ? `?${new URLSearchParams(filter as Record).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) => { + 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) => { + 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, + } +} diff --git a/frontends/nextjs/src/hooks/useFileTree.ts b/frontends/nextjs/src/hooks/useFileTree.ts new file mode 100644 index 000000000..94d21b0c6 --- /dev/null +++ b/frontends/nextjs/src/hooks/useFileTree.ts @@ -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 + 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(initialTree) + const [expandedPaths, setExpandedPaths] = useState>(new Set()) + const [selectedPath, setSelectedPath] = useState(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, + } +} diff --git a/frontends/nextjs/src/hooks/useGitHubFetcher.ts b/frontends/nextjs/src/hooks/useGitHubFetcher.ts new file mode 100644 index 000000000..6f91b15c7 --- /dev/null +++ b/frontends/nextjs/src/hooks/useGitHubFetcher.ts @@ -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 +} + +export function useGitHubFetcher( + owner?: string, + repo?: string, + autoRefresh = false +): UseGitHubFetcherReturn { + const [runs, setRuns] = useState([]) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(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, + } +} diff --git a/frontends/nextjs/src/hooks/useKV.ts b/frontends/nextjs/src/hooks/useKV.ts new file mode 100644 index 000000000..2e5a535e0 --- /dev/null +++ b/frontends/nextjs/src/hooks/useKV.ts @@ -0,0 +1,97 @@ +/** + * Hook for key-value storage operations + */ +import { useState, useCallback } from 'react' + +export interface UseKVReturn { + get: (key: string) => Promise + set: (key: string, value: string) => Promise + delete: (key: string) => Promise + list: (prefix?: string) => Promise + isLoading: boolean + error: Error | null +} + +export function useKV(): UseKVReturn { + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(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, + } +} diff --git a/frontends/nextjs/src/lib/auth/api/fetch-session.ts b/frontends/nextjs/src/lib/auth/api/fetch-session.ts new file mode 100644 index 000000000..619605017 --- /dev/null +++ b/frontends/nextjs/src/lib/auth/api/fetch-session.ts @@ -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 { + 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 + } +} diff --git a/frontends/nextjs/src/lib/auth/api/login.ts b/frontends/nextjs/src/lib/auth/api/login.ts new file mode 100644 index 000000000..01b75134a --- /dev/null +++ b/frontends/nextjs/src/lib/auth/api/login.ts @@ -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 { + 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 +} diff --git a/frontends/nextjs/src/lib/auth/api/logout.ts b/frontends/nextjs/src/lib/auth/api/logout.ts new file mode 100644 index 000000000..bbd06988e --- /dev/null +++ b/frontends/nextjs/src/lib/auth/api/logout.ts @@ -0,0 +1,15 @@ +/** + * @file logout.ts + * @description User logout API + */ + +export async function logout(): Promise { + const response = await fetch('/api/auth/logout', { + method: 'POST', + credentials: 'include', + }) + + if (!response.ok) { + throw new Error('Logout failed') + } +} diff --git a/frontends/nextjs/src/lib/auth/api/register.ts b/frontends/nextjs/src/lib/auth/api/register.ts new file mode 100644 index 000000000..79428c5f8 --- /dev/null +++ b/frontends/nextjs/src/lib/auth/api/register.ts @@ -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 { + 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 +} diff --git a/frontends/nextjs/src/lib/db/app-config/get-app-config.ts b/frontends/nextjs/src/lib/db/app-config/get-app-config.ts index 3f473e51f..39091b78a 100644 --- a/frontends/nextjs/src/lib/db/app-config/get-app-config.ts +++ b/frontends/nextjs/src/lib/db/app-config/get-app-config.ts @@ -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 { diff --git a/frontends/nextjs/src/lib/db/app-config/set-app-config.ts b/frontends/nextjs/src/lib/db/app-config/set-app-config.ts index e9a0070e1..579023708 100644 --- a/frontends/nextjs/src/lib/db/app-config/set-app-config.ts +++ b/frontends/nextjs/src/lib/db/app-config/set-app-config.ts @@ -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 { diff --git a/frontends/nextjs/src/lib/db/auth/queries/authenticate-user.ts b/frontends/nextjs/src/lib/db/auth/queries/authenticate-user.ts index 35dfc2ed9..407ca68a1 100644 --- a/frontends/nextjs/src/lib/db/auth/queries/authenticate-user.ts +++ b/frontends/nextjs/src/lib/db/auth/queries/authenticate-user.ts @@ -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' diff --git a/frontends/nextjs/src/lib/db/auth/queries/get-user-by-email.ts b/frontends/nextjs/src/lib/db/auth/queries/get-user-by-email.ts index 7d89a4dc1..5f36c7ae4 100644 --- a/frontends/nextjs/src/lib/db/auth/queries/get-user-by-email.ts +++ b/frontends/nextjs/src/lib/db/auth/queries/get-user-by-email.ts @@ -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' /** diff --git a/frontends/nextjs/src/lib/db/auth/queries/get-user-by-username.ts b/frontends/nextjs/src/lib/db/auth/queries/get-user-by-username.ts index 179117324..d32c51316 100644 --- a/frontends/nextjs/src/lib/db/auth/queries/get-user-by-username.ts +++ b/frontends/nextjs/src/lib/db/auth/queries/get-user-by-username.ts @@ -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' /** diff --git a/frontends/nextjs/src/lib/db/comments/crud/add-comment.test.ts b/frontends/nextjs/src/lib/db/comments/crud/add-comment.test.ts index e26d4ad95..59d2e9332 100644 --- a/frontends/nextjs/src/lib/db/comments/crud/add-comment.test.ts +++ b/frontends/nextjs/src/lib/db/comments/crud/add-comment.test.ts @@ -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 } diff --git a/frontends/nextjs/src/lib/db/comments/crud/add-comment.ts b/frontends/nextjs/src/lib/db/comments/crud/add-comment.ts index 3b1e0618f..f6d25d57b 100644 --- a/frontends/nextjs/src/lib/db/comments/crud/add-comment.ts +++ b/frontends/nextjs/src/lib/db/comments/crud/add-comment.ts @@ -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 diff --git a/frontends/nextjs/src/lib/db/comments/crud/get-comments.ts b/frontends/nextjs/src/lib/db/comments/crud/get-comments.ts index f1f7b54f6..b51bf7545 100644 --- a/frontends/nextjs/src/lib/db/comments/crud/get-comments.ts +++ b/frontends/nextjs/src/lib/db/comments/crud/get-comments.ts @@ -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 diff --git a/frontends/nextjs/src/lib/db/comments/crud/set-comments.ts b/frontends/nextjs/src/lib/db/comments/crud/set-comments.ts index 8f0a586d4..bba695f0c 100644 --- a/frontends/nextjs/src/lib/db/comments/crud/set-comments.ts +++ b/frontends/nextjs/src/lib/db/comments/crud/set-comments.ts @@ -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 diff --git a/frontends/nextjs/src/lib/db/comments/crud/update-comment.test.ts b/frontends/nextjs/src/lib/db/comments/crud/update-comment.test.ts index 88cc62bf0..87b62b166 100644 --- a/frontends/nextjs/src/lib/db/comments/crud/update-comment.test.ts +++ b/frontends/nextjs/src/lib/db/comments/crud/update-comment.test.ts @@ -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 } diff --git a/frontends/nextjs/src/lib/db/comments/crud/update-comment.ts b/frontends/nextjs/src/lib/db/comments/crud/update-comment.ts index 96d7b93e0..d6f73e079 100644 --- a/frontends/nextjs/src/lib/db/comments/crud/update-comment.ts +++ b/frontends/nextjs/src/lib/db/comments/crud/update-comment.ts @@ -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 diff --git a/frontends/nextjs/src/lib/db/pages/crud/add-page.test.ts b/frontends/nextjs/src/lib/db/pages/crud/add-page.test.ts index 7e14da5d3..daa1da832 100644 --- a/frontends/nextjs/src/lib/db/pages/crud/add-page.test.ts +++ b/frontends/nextjs/src/lib/db/pages/crud/add-page.test.ts @@ -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 } diff --git a/frontends/nextjs/src/lib/db/pages/crud/add-page.ts b/frontends/nextjs/src/lib/db/pages/crud/add-page.ts index 48696684f..1a16be03b 100644 --- a/frontends/nextjs/src/lib/db/pages/crud/add-page.ts +++ b/frontends/nextjs/src/lib/db/pages/crud/add-page.ts @@ -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 diff --git a/frontends/nextjs/src/lib/db/pages/crud/get-pages.ts b/frontends/nextjs/src/lib/db/pages/crud/get-pages.ts index e08c2d7dc..3fc4aec43 100644 --- a/frontends/nextjs/src/lib/db/pages/crud/get-pages.ts +++ b/frontends/nextjs/src/lib/db/pages/crud/get-pages.ts @@ -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 diff --git a/frontends/nextjs/src/lib/db/pages/crud/set-pages.test.ts b/frontends/nextjs/src/lib/db/pages/crud/set-pages.test.ts index 0afb245cd..e82947b5f 100644 --- a/frontends/nextjs/src/lib/db/pages/crud/set-pages.test.ts +++ b/frontends/nextjs/src/lib/db/pages/crud/set-pages.test.ts @@ -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() diff --git a/frontends/nextjs/src/lib/db/pages/crud/set-pages.ts b/frontends/nextjs/src/lib/db/pages/crud/set-pages.ts index dd7d0b1c2..d764e90fe 100644 --- a/frontends/nextjs/src/lib/db/pages/crud/set-pages.ts +++ b/frontends/nextjs/src/lib/db/pages/crud/set-pages.ts @@ -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) diff --git a/frontends/nextjs/src/lib/db/pages/crud/update-page.ts b/frontends/nextjs/src/lib/db/pages/crud/update-page.ts index d2d44527d..603d873bf 100644 --- a/frontends/nextjs/src/lib/db/pages/crud/update-page.ts +++ b/frontends/nextjs/src/lib/db/pages/crud/update-page.ts @@ -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 diff --git a/frontends/nextjs/src/lib/db/schemas/crud/update-schema.ts b/frontends/nextjs/src/lib/db/schemas/crud/update-schema.ts index a9a4f62f1..52612ced1 100644 --- a/frontends/nextjs/src/lib/db/schemas/crud/update-schema.ts +++ b/frontends/nextjs/src/lib/db/schemas/crud/update-schema.ts @@ -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 diff --git a/frontends/nextjs/src/lib/db/tenants/crud/add-tenant.ts b/frontends/nextjs/src/lib/db/tenants/crud/add-tenant.ts index 0c2f2bbc5..8159b072a 100644 --- a/frontends/nextjs/src/lib/db/tenants/crud/add-tenant.ts +++ b/frontends/nextjs/src/lib/db/tenants/crud/add-tenant.ts @@ -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 diff --git a/frontends/nextjs/src/lib/db/tenants/crud/get-tenants.ts b/frontends/nextjs/src/lib/db/tenants/crud/get-tenants.ts index f5f76b4ea..9ce71f2e5 100644 --- a/frontends/nextjs/src/lib/db/tenants/crud/get-tenants.ts +++ b/frontends/nextjs/src/lib/db/tenants/crud/get-tenants.ts @@ -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 diff --git a/frontends/nextjs/src/lib/db/tenants/crud/set-tenants.ts b/frontends/nextjs/src/lib/db/tenants/crud/set-tenants.ts index e2cc8bac7..fb4d66ab8 100644 --- a/frontends/nextjs/src/lib/db/tenants/crud/set-tenants.ts +++ b/frontends/nextjs/src/lib/db/tenants/crud/set-tenants.ts @@ -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) diff --git a/frontends/nextjs/src/lib/db/tenants/crud/update-tenant.ts b/frontends/nextjs/src/lib/db/tenants/crud/update-tenant.ts index c61bfd735..f81f9a380 100644 --- a/frontends/nextjs/src/lib/db/tenants/crud/update-tenant.ts +++ b/frontends/nextjs/src/lib/db/tenants/crud/update-tenant.ts @@ -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 diff --git a/frontends/nextjs/src/lib/db/users/crud/add/add-user.ts b/frontends/nextjs/src/lib/db/users/crud/add/add-user.ts index 879c83b71..eb9b4ebaf 100644 --- a/frontends/nextjs/src/lib/db/users/crud/add/add-user.ts +++ b/frontends/nextjs/src/lib/db/users/crud/add/add-user.ts @@ -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 diff --git a/frontends/nextjs/src/lib/db/users/crud/update-user.ts b/frontends/nextjs/src/lib/db/users/crud/update-user.ts index e65b4ee8a..214b04e25 100644 --- a/frontends/nextjs/src/lib/db/users/crud/update-user.ts +++ b/frontends/nextjs/src/lib/db/users/crud/update-user.ts @@ -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 diff --git a/frontends/nextjs/src/lib/db/users/getters/get-user-by-id.ts b/frontends/nextjs/src/lib/db/users/getters/get-user-by-id.ts index bce9f30fa..956f5ac27 100644 --- a/frontends/nextjs/src/lib/db/users/getters/get-user-by-id.ts +++ b/frontends/nextjs/src/lib/db/users/getters/get-user-by-id.ts @@ -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' /** diff --git a/frontends/nextjs/src/lib/db/users/getters/get-users.ts b/frontends/nextjs/src/lib/db/users/getters/get-users.ts index 87ef855de..a13088a77 100644 --- a/frontends/nextjs/src/lib/db/users/getters/get-users.ts +++ b/frontends/nextjs/src/lib/db/users/getters/get-users.ts @@ -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' } diff --git a/frontends/nextjs/src/lib/db/users/map-user-record.ts b/frontends/nextjs/src/lib/db/users/map-user-record.ts index adde43fa4..e4d9f92a0 100644 --- a/frontends/nextjs/src/lib/db/users/map-user-record.ts +++ b/frontends/nextjs/src/lib/db/users/map-user-record.ts @@ -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. diff --git a/frontends/nextjs/src/lib/db/users/set-users.ts b/frontends/nextjs/src/lib/db/users/set-users.ts index a6cba524c..c74ca9c41 100644 --- a/frontends/nextjs/src/lib/db/users/set-users.ts +++ b/frontends/nextjs/src/lib/db/users/set-users.ts @@ -1,4 +1,4 @@ -import type { User } from '../../types/level-types' +import type { User } from '@/lib/level-types' import { getAdapter } from '../core/dbal-client' /** diff --git a/frontends/nextjs/src/lib/db/users/super-god/get-super-god.ts b/frontends/nextjs/src/lib/db/users/super-god/get-super-god.ts index 27950dbee..6bc12d25f 100644 --- a/frontends/nextjs/src/lib/db/users/super-god/get-super-god.ts +++ b/frontends/nextjs/src/lib/db/users/super-god/get-super-god.ts @@ -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' /** diff --git a/frontends/nextjs/src/lib/db/workflows/crud/add-workflow.ts b/frontends/nextjs/src/lib/db/workflows/crud/add-workflow.ts index 5f73ea12c..5d747c585 100644 --- a/frontends/nextjs/src/lib/db/workflows/crud/add-workflow.ts +++ b/frontends/nextjs/src/lib/db/workflows/crud/add-workflow.ts @@ -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 diff --git a/frontends/nextjs/src/lib/db/workflows/crud/get-workflows.ts b/frontends/nextjs/src/lib/db/workflows/crud/get-workflows.ts index c379a89db..27f8a0993 100644 --- a/frontends/nextjs/src/lib/db/workflows/crud/get-workflows.ts +++ b/frontends/nextjs/src/lib/db/workflows/crud/get-workflows.ts @@ -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 diff --git a/frontends/nextjs/src/lib/db/workflows/crud/set-workflows.test.ts b/frontends/nextjs/src/lib/db/workflows/crud/set-workflows.test.ts index b8ab7d041..5ef68a938 100644 --- a/frontends/nextjs/src/lib/db/workflows/crud/set-workflows.test.ts +++ b/frontends/nextjs/src/lib/db/workflows/crud/set-workflows.test.ts @@ -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() diff --git a/frontends/nextjs/src/lib/db/workflows/crud/set-workflows.ts b/frontends/nextjs/src/lib/db/workflows/crud/set-workflows.ts index c92f7dcb2..54dec03ae 100644 --- a/frontends/nextjs/src/lib/db/workflows/crud/set-workflows.ts +++ b/frontends/nextjs/src/lib/db/workflows/crud/set-workflows.ts @@ -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 diff --git a/frontends/nextjs/src/lib/db/workflows/crud/update-workflow.test.ts b/frontends/nextjs/src/lib/db/workflows/crud/update-workflow.test.ts index 7e16ebd1a..8b7d8147e 100644 --- a/frontends/nextjs/src/lib/db/workflows/crud/update-workflow.test.ts +++ b/frontends/nextjs/src/lib/db/workflows/crud/update-workflow.test.ts @@ -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 } diff --git a/frontends/nextjs/src/lib/db/workflows/crud/update-workflow.ts b/frontends/nextjs/src/lib/db/workflows/crud/update-workflow.ts index 199dc452e..d2cd74658 100644 --- a/frontends/nextjs/src/lib/db/workflows/crud/update-workflow.ts +++ b/frontends/nextjs/src/lib/db/workflows/crud/update-workflow.ts @@ -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 diff --git a/frontends/nextjs/src/lib/types/level-types.ts b/frontends/nextjs/src/lib/types/level-types.ts new file mode 100644 index 000000000..44391e663 --- /dev/null +++ b/frontends/nextjs/src/lib/types/level-types.ts @@ -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 +} diff --git a/frontends/nextjs/src/lib/types/schema-types.ts b/frontends/nextjs/src/lib/types/schema-types.ts new file mode 100644 index 000000000..be06a7337 --- /dev/null +++ b/frontends/nextjs/src/lib/types/schema-types.ts @@ -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 + 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 + hooks?: Record | string +} + +export interface DynamicData { + id: string + tenantId?: string + schemaId: string + data: string // JSON data + createdAt: number + updatedAt?: number +}