From d675825df4c29eafbf9366f900ff1279def2d9f0 Mon Sep 17 00:00:00 2001 From: JohnDoe6345789 Date: Fri, 26 Dec 2025 01:22:02 +0000 Subject: [PATCH] code: nextjs,lua,frontends (3 files) --- dbal/ts/tests/core/client-lua-scripts.test.ts | 125 ++++++++++++++++++ frontends/nextjs/src/app/api/levels/route.ts | 16 +++ .../lua/execute-lua-script-with-profile.ts | 33 +++++ 3 files changed, 174 insertions(+) create mode 100644 dbal/ts/tests/core/client-lua-scripts.test.ts create mode 100644 frontends/nextjs/src/app/api/levels/route.ts create mode 100644 frontends/nextjs/src/lib/lua/execute-lua-script-with-profile.ts diff --git a/dbal/ts/tests/core/client-lua-scripts.test.ts b/dbal/ts/tests/core/client-lua-scripts.test.ts new file mode 100644 index 000000000..7834d61f5 --- /dev/null +++ b/dbal/ts/tests/core/client-lua-scripts.test.ts @@ -0,0 +1,125 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { DBALClient } from '../../src/core/client' +import { DBALError, DBALErrorCode } from '../../src/core/errors' + +const mockAdapter = vi.hoisted(() => ({ + create: vi.fn(), + read: vi.fn(), + update: vi.fn(), + delete: vi.fn(), + list: vi.fn(), + findFirst: vi.fn(), + findByField: vi.fn(), + upsert: vi.fn(), + updateByField: vi.fn(), + deleteByField: vi.fn(), + deleteMany: vi.fn(), + createMany: vi.fn(), + updateMany: vi.fn(), + getCapabilities: vi.fn(), + close: vi.fn(), +})) + +vi.mock('../../src/adapters/prisma-adapter', () => ({ + PrismaAdapter: vi.fn(() => mockAdapter), +})) + +const baseConfig = { + mode: 'development' as const, + adapter: 'prisma' as const, + database: { url: 'file:memory' }, +} + +const luaScriptInput = { + name: 'health_check', + description: 'Simple health check', + code: 'return true', + isSandboxed: true, + allowedGlobals: ['math'], + timeoutMs: 1000, + createdBy: '11111111-1111-1111-1111-111111111111', +} + +const luaScriptRecord = { + ...luaScriptInput, + id: '22222222-2222-2222-2222-222222222222', + createdAt: new Date('2024-01-01T00:00:00.000Z'), + updatedAt: new Date('2024-01-02T00:00:00.000Z'), +} + +beforeEach(() => { + Object.values(mockAdapter).forEach(value => { + if (typeof value === 'function' && 'mockReset' in value) { + value.mockReset() + } + }) +}) + +describe('DBALClient luaScripts', () => { + it('creates lua scripts via adapter', async () => { + mockAdapter.create.mockResolvedValue(luaScriptRecord) + + const client = new DBALClient(baseConfig) + const result = await client.luaScripts.create(luaScriptInput) + + expect(mockAdapter.create).toHaveBeenCalledWith('LuaScript', luaScriptInput) + expect(result).toEqual(luaScriptRecord) + }) + + it('rejects invalid lua script data before adapter call', async () => { + const client = new DBALClient(baseConfig) + + await expect(client.luaScripts.create({ + ...luaScriptInput, + name: '', + })).rejects.toMatchObject({ code: DBALErrorCode.VALIDATION_ERROR }) + + expect(mockAdapter.create).not.toHaveBeenCalled() + }) + + it('maps lua script create conflicts to friendly message', async () => { + mockAdapter.create.mockRejectedValue(DBALError.conflict('unique violation')) + + const client = new DBALClient(baseConfig) + + await expect(client.luaScripts.create(luaScriptInput)).rejects.toMatchObject({ + code: DBALErrorCode.CONFLICT, + message: "Lua script with name 'health_check' already exists", + }) + }) + + it('throws not found when reading missing lua scripts', async () => { + mockAdapter.read.mockResolvedValue(null) + + const client = new DBALClient(baseConfig) + + await expect(client.luaScripts.read(luaScriptRecord.id)).rejects.toMatchObject({ + code: DBALErrorCode.NOT_FOUND, + }) + }) + + it('updates, deletes, and lists lua scripts', async () => { + const updatedRecord = { ...luaScriptRecord, timeoutMs: 2000 } + mockAdapter.update.mockResolvedValue(updatedRecord) + mockAdapter.delete.mockResolvedValue(true) + mockAdapter.list.mockResolvedValue({ + data: [luaScriptRecord], + total: 1, + page: 1, + limit: 20, + hasMore: false, + }) + + const client = new DBALClient(baseConfig) + + const updateResult = await client.luaScripts.update(luaScriptRecord.id, { timeoutMs: 2000 }) + expect(updateResult).toEqual(updatedRecord) + + const deleteResult = await client.luaScripts.delete(luaScriptRecord.id) + expect(deleteResult).toBe(true) + + const listResult = await client.luaScripts.list() + expect(listResult.data).toEqual([luaScriptRecord]) + expect(mockAdapter.list).toHaveBeenCalledWith('LuaScript', undefined) + }) +}) diff --git a/frontends/nextjs/src/app/api/levels/route.ts b/frontends/nextjs/src/app/api/levels/route.ts new file mode 100644 index 000000000..0c77bd3f2 --- /dev/null +++ b/frontends/nextjs/src/app/api/levels/route.ts @@ -0,0 +1,16 @@ +import { NextResponse } from 'next/server' + +import { PERMISSION_LEVELS } from '@/app/levels/levels-data' + +export async function GET(request: Request) { + const url = new URL(request.url) + const rawLevel = url.searchParams.get('level') + const normalized = rawLevel?.toLowerCase().trim() + const filtered = normalized + ? PERMISSION_LEVELS.filter( + (level) => level.key === normalized || String(level.id) === normalized + ) + : PERMISSION_LEVELS + + return NextResponse.json({ levels: filtered }) +} diff --git a/frontends/nextjs/src/lib/lua/execute-lua-script-with-profile.ts b/frontends/nextjs/src/lib/lua/execute-lua-script-with-profile.ts new file mode 100644 index 000000000..621485618 --- /dev/null +++ b/frontends/nextjs/src/lib/lua/execute-lua-script-with-profile.ts @@ -0,0 +1,33 @@ +import type { LuaScript } from '../types/level-types' +import type { LuaExecutionContext, LuaExecutionResult } from './functions/types' +import { createLuaEngine } from './create-lua-engine' +import { createSandboxedLuaEngine } from './create-sandboxed-lua-engine' + +type LuaScriptProfile = Pick + +export async function executeLuaScriptWithProfile( + code: string, + context: LuaExecutionContext = {}, + profile: LuaScriptProfile = {} +): Promise { + const isSandboxed = profile.isSandboxed ?? true + + if (!isSandboxed) { + const engine = createLuaEngine() + try { + return await engine.execute(code, context) + } finally { + engine.destroy() + } + } + + const engine = createSandboxedLuaEngine(profile.timeoutMs ?? 5000) + engine.setAllowedGlobals(profile.allowedGlobals) + + try { + const result = await engine.executeWithSandbox(code, context) + return result.execution + } finally { + engine.destroy() + } +}