feat: refactor LuaSnippetUtils to use utility functions for category counts and tags

This commit is contained in:
2025-12-25 18:45:31 +00:00
parent c7ef4ae15b
commit 2470f50fa8
4 changed files with 50 additions and 170 deletions

View File

@@ -1,159 +1,14 @@
/**
* DBAL Client Singleton
*
*
* Provides centralized access to the Database Abstraction Layer.
* All db/ lambda functions should use this instead of importing Prisma directly.
*
*
* This uses the PrismaClient directly but wraps it in a DBAL-compatible interface,
* providing a migration path to the full DBAL when ready.
*/
import { prisma } from '../prisma'
export type { DBALAdapter, ListOptions, ListResult } from './dbal-client/types'
export interface ListOptions {
filter?: Record<string, unknown>
sort?: Record<string, 'asc' | 'desc'>
page?: number
limit?: number
}
export interface ListResult<T> {
data: T[]
total?: number
page?: number
limit?: number
hasMore?: boolean
}
export interface DBALAdapter {
create(entity: string, data: Record<string, unknown>): Promise<unknown>
read(entity: string, id: string): Promise<unknown | null>
update(entity: string, id: string, data: Record<string, unknown>): Promise<unknown>
delete(entity: string, id: string): Promise<boolean>
list(entity: string, options?: ListOptions): Promise<ListResult<unknown>>
findFirst(entity: string, options?: { where?: Record<string, unknown> }): Promise<unknown | null>
upsert(entity: string, options: { where: Record<string, unknown>; update: Record<string, unknown>; create: Record<string, unknown> }): Promise<unknown>
close(): Promise<void>
}
/**
* Get the Prisma model by entity name
*/
const getModel = (entity: string): any => {
const modelName = entity.charAt(0).toLowerCase() + entity.slice(1)
const model = (prisma as any)[modelName]
if (!model) {
throw new Error(`Entity ${entity} not found in Prisma schema`)
}
return model
}
/**
* Build where clause from filter
*/
const buildWhereClause = (filter: Record<string, unknown>): Record<string, unknown> => {
const where: Record<string, unknown> = {}
for (const [key, value] of Object.entries(filter)) {
if (value !== undefined) {
where[key] = value
}
}
return where
}
/**
* DBAL Adapter implementation using Prisma
*/
const prismaAdapter: DBALAdapter = {
async create(entity: string, data: Record<string, unknown>): Promise<unknown> {
const model = getModel(entity)
return model.create({ data })
},
async read(entity: string, id: string): Promise<unknown | null> {
const model = getModel(entity)
return model.findUnique({ where: { id } })
},
async update(entity: string, id: string, data: Record<string, unknown>): Promise<unknown> {
const model = getModel(entity)
// Filter out undefined values
const cleanData: Record<string, unknown> = {}
for (const [key, value] of Object.entries(data)) {
if (value !== undefined) {
cleanData[key] = value
}
}
return model.update({ where: { id }, data: cleanData })
},
async delete(entity: string, id: string): Promise<boolean> {
const model = getModel(entity)
try {
await model.delete({ where: { id } })
return true
} catch {
return false
}
},
async list(entity: string, options?: ListOptions): Promise<ListResult<unknown>> {
const model = getModel(entity)
const page = options?.page || 1
const limit = options?.limit || 1000
const skip = (page - 1) * limit
const where = options?.filter ? buildWhereClause(options.filter) : undefined
const orderBy = options?.sort
const [data, total] = await Promise.all([
model.findMany({
where,
orderBy,
skip,
take: limit,
}),
model.count({ where }),
])
return {
data,
total,
page,
limit,
hasMore: skip + limit < total,
}
},
async findFirst(entity: string, options?: { where?: Record<string, unknown> }): Promise<unknown | null> {
const model = getModel(entity)
const where = options?.where ? buildWhereClause(options.where) : undefined
return model.findFirst({ where })
},
async upsert(entity: string, options: { where: Record<string, unknown>; update: Record<string, unknown>; create: Record<string, unknown> }): Promise<unknown> {
const model = getModel(entity)
return model.upsert({
where: options.where,
update: options.update,
create: options.create,
})
},
async close(): Promise<void> {
await prisma.$disconnect()
},
}
/**
* Get the DBAL adapter singleton for database operations
*/
export const getAdapter = (): DBALAdapter => {
return prismaAdapter
}
/**
* Close the DBAL adapter connection
*/
export const closeAdapter = async (): Promise<void> => {
await prismaAdapter.close()
}
export { getAdapter } from './dbal-client/get-adapter'
export { closeAdapter } from './dbal-client/close-adapter'

View File

@@ -2,6 +2,8 @@ import { LUA_SNIPPETS, LUA_SNIPPET_CATEGORIES, type LuaSnippet } from './snippet
import { getSnippetsByCategory } from './functions/get-snippets-by-category'
import { searchSnippets } from './functions/search-snippets'
import { getSnippetById } from './functions/get-snippet-by-id'
import { getSnippetCategoryCounts } from './functions/get-snippet-category-counts'
import { getAllSnippetTags } from './functions/get-all-snippet-tags'
/**
* LuaSnippetUtils - Class wrapper for Lua snippet utility functions
@@ -39,30 +41,12 @@ export class LuaSnippetUtils {
/**
* Get count of snippets per category
*/
static getCategoryCounts(): Record<string, number> {
const counts: Record<string, number> = { All: LUA_SNIPPETS.length }
for (const snippet of LUA_SNIPPETS) {
counts[snippet.category] = (counts[snippet.category] || 0) + 1
}
return counts
}
static getCategoryCounts = getSnippetCategoryCounts
/**
* Get all unique tags across snippets
*/
static getAllTags(): string[] {
const tagSet = new Set<string>()
for (const snippet of LUA_SNIPPETS) {
for (const tag of snippet.tags) {
tagSet.add(tag)
}
}
return Array.from(tagSet).sort()
}
static getAllTags = getAllSnippetTags
}
// Re-export types for convenience

View File

@@ -21,3 +21,5 @@ export { executeLuaCode } from './execution/execute-lua-code'
export { getSnippetsByCategory } from './get-snippets-by-category'
export { searchSnippets } from './search-snippets'
export { getSnippetById } from './get-snippet-by-id'
export { getSnippetCategoryCounts } from './get-snippet-category-counts'
export { getAllSnippetTags } from './get-all-snippet-tags'

View File

@@ -0,0 +1,39 @@
import type { SecurityScanResult } from '../types'
import { scanHTML } from './scan-html'
import { scanJavaScript } from './scan-javascript'
import { scanJSON } from './scan-json'
import { scanLua } from './scan-lua'
/**
* Convenience function to scan code for vulnerabilities
* Automatically detects type based on content or explicit type parameter
*/
export const scanForVulnerabilities = (
code: string,
type?: 'javascript' | 'lua' | 'json' | 'html'
): SecurityScanResult => {
let resolvedType = type
if (!resolvedType) {
if (code.trim().startsWith('{') || code.trim().startsWith('[')) {
resolvedType = 'json'
} else if (code.includes('function') && code.includes('end')) {
resolvedType = 'lua'
} else if (code.includes('<') && code.includes('>')) {
resolvedType = 'html'
} else {
resolvedType = 'javascript'
}
}
switch (resolvedType) {
case 'lua':
return scanLua(code)
case 'json':
return scanJSON(code)
case 'html':
return scanHTML(code)
default:
return scanJavaScript(code)
}
}