update: packages,lua,log (12 files)

This commit is contained in:
Richard Ward
2025-12-30 13:15:23 +00:00
parent 03b627a85c
commit ea6bd1c69a
12 changed files with 399 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
-- Format all logs for display
local formatLogEntry = require("formatting.format_log_entry")
---@class FormatAllLogs
local M = {}
---Format all logs in an array for display
---@param logs AuditLog[]? Array of raw log entries
---@return FormattedLogEntry[] Array of formatted entries
function M.formatAllLogs(logs)
local result = {}
for i, log in ipairs(logs or {}) do
result[i] = formatLogEntry.formatLogEntry(log)
end
return result
end
return M

View File

@@ -0,0 +1,30 @@
-- Format a single log entry for display
local getOperationColor = require("formatting.get_operation_color")
local getResourceIcon = require("formatting.get_resource_icon")
local formatTimestamp = require("formatting.format_timestamp")
---@class FormatLogEntry
local M = {}
---Format a log entry for display in a table
---@param log AuditLog Raw log entry
---@return FormattedLogEntry Formatted entry with display strings
function M.formatLogEntry(log)
return {
id = log.id,
operation = log.operation,
operationColor = getOperationColor.getOperationColor(log.operation),
resource = log.resource,
resourceId = log.resourceId,
resourceIcon = getResourceIcon.getResourceIcon(log.resource),
username = log.username,
timestamp = formatTimestamp.formatTimestamp(log.timestamp),
ipAddress = log.ipAddress,
success = log.success,
errorMessage = log.errorMessage,
rowClass = log.success and "bg-card" or "bg-destructive/5 border-destructive/20"
}
end
return M

View File

@@ -0,0 +1,19 @@
-- Format timestamp for display
---@class FormatTimestamp
local M = {}
---Format a timestamp (in milliseconds) for display
---@param timestamp number? Unix timestamp in milliseconds
---@return string Formatted date/time string or "Unknown"
function M.formatTimestamp(timestamp)
if not timestamp then
return "Unknown"
end
-- Assuming timestamp is in milliseconds
local seconds = math.floor(timestamp / 1000)
return os.date("%Y-%m-%d %H:%M:%S", seconds)
end
return M

View File

@@ -0,0 +1,15 @@
-- Get color class for operation type
local mappings = require("formatting.mappings")
---@class GetOperationColor
local M = {}
---Get CSS color class for an operation type
---@param operation string Operation name (CREATE, READ, UPDATE, DELETE)
---@return string CSS class for the operation badge
function M.getOperationColor(operation)
return mappings.operationColors[operation] or "bg-gray-500"
end
return M

View File

@@ -0,0 +1,15 @@
-- Get icon name for resource type
local mappings = require("formatting.mappings")
---@class GetResourceIcon
local M = {}
---Get icon name for a resource type
---@param resource string Resource type name
---@return string Icon name for the resource
function M.getResourceIcon(resource)
return mappings.resourceIcons[resource] or mappings.resourceIcons.default
end
return M

View File

@@ -0,0 +1,16 @@
-- Get status badge text for a log
---@class GetStatusBadge
local M = {}
---Get status badge text for a log entry
---@param log AuditLog Log entry to check
---@return string? Badge text or nil if successful
function M.getStatusBadge(log)
if log.success then
return nil
end
return "Failed"
end
return M

View File

@@ -0,0 +1,34 @@
-- Audit log formatting module
-- Facade that re-exports all formatting functions
local mappings = require("formatting.mappings")
local getOperationColor = require("formatting.get_operation_color")
local getResourceIcon = require("formatting.get_resource_icon")
local formatTimestamp = require("formatting.format_timestamp")
local formatLogEntry = require("formatting.format_log_entry")
local formatAllLogs = require("formatting.format_all_logs")
local getStatusBadge = require("formatting.get_status_badge")
---@class FormattingModule
---@field operationColors table<string, string>
---@field resourceIcons table<string, string>
---@field getOperationColor fun(operation: string): string
---@field getResourceIcon fun(resource: string): string
---@field formatTimestamp fun(timestamp: number?): string
---@field formatLogEntry fun(log: AuditLog): FormattedLogEntry
---@field formatAllLogs fun(logs: AuditLog[]?): FormattedLogEntry[]
---@field getStatusBadge fun(log: AuditLog): string?
---@type FormattingModule
local M = {
operationColors = mappings.operationColors,
resourceIcons = mappings.resourceIcons,
getOperationColor = getOperationColor.getOperationColor,
getResourceIcon = getResourceIcon.getResourceIcon,
formatTimestamp = formatTimestamp.formatTimestamp,
formatLogEntry = formatLogEntry.formatLogEntry,
formatAllLogs = formatAllLogs.formatAllLogs,
getStatusBadge = getStatusBadge.getStatusBadge,
}
return M

View File

@@ -0,0 +1,23 @@
-- Color and icon mappings for audit log display
---@class Mappings
local M = {}
--- Operation type to color mapping
---@type table<string, string>
M.operationColors = {
CREATE = "bg-green-500",
READ = "bg-blue-500",
UPDATE = "bg-yellow-500",
DELETE = "bg-red-500"
}
--- Resource type to icon mapping
---@type table<string, string>
M.resourceIcons = {
user = "User",
credential = "ShieldCheck",
default = "ChartLine"
}
return M

View File

@@ -0,0 +1,28 @@
-- Shared types for audit log formatting
---@class AuditLog
---@field id string Unique log entry ID
---@field operation string Operation type (create, read, update, delete)
---@field resource string Resource type being accessed
---@field resourceId string ID of the resource
---@field username string User who performed the action
---@field timestamp number Unix timestamp in milliseconds
---@field ipAddress string IP address of the request
---@field success boolean Whether the operation succeeded
---@field errorMessage string? Error message if failed
---@class FormattedLogEntry
---@field id string
---@field operation string
---@field operationColor string CSS class for operation badge
---@field resource string
---@field resourceId string
---@field resourceIcon string Icon name for resource type
---@field username string
---@field timestamp string Formatted date/time string
---@field ipAddress string
---@field success boolean
---@field errorMessage string?
---@field rowClass string CSS class for table row
return {}

View File

@@ -0,0 +1,70 @@
-- Fields tests for schema_editor package
-- Tests field type builders
local string_field = require("fields/string")
local number_field = require("fields/number")
local boolean_field = require("fields/boolean")
local array_field = require("fields/array")
describe("Schema Field Builders", function()
describe("string_field", function()
it.each({
{ name = "email", required = nil, min = nil, max = nil, expectedRequired = false },
{ name = "username", required = true, min = 3, max = 20, expectedRequired = true },
})("should create string field $name", function(testCase)
local result = string_field(testCase.name, testCase.required, testCase.min, testCase.max)
expect(result.type).toBe("string")
expect(result.name).toBe(testCase.name)
expect(result.required).toBe(testCase.expectedRequired)
if testCase.min then
expect(result.minLength).toBe(testCase.min)
end
if testCase.max then
expect(result.maxLength).toBe(testCase.max)
end
end)
end)
describe("number_field", function()
it.each({
{ name = "age", required = nil, min = nil, max = nil, expectedRequired = false },
{ name = "quantity", required = true, min = 0, max = 1000, expectedRequired = true },
})("should create number field $name", function(testCase)
local result = number_field(testCase.name, testCase.required, testCase.min, testCase.max)
expect(result.type).toBe("number")
expect(result.name).toBe(testCase.name)
expect(result.required).toBe(testCase.expectedRequired)
if testCase.min then
expect(result.min).toBe(testCase.min)
end
if testCase.max then
expect(result.max).toBe(testCase.max)
end
end)
end)
describe("boolean_field", function()
it.each({
{ name = "active", required = nil, default = nil, expectedRequired = false },
{ name = "verified", required = true, default = false, expectedRequired = true },
})("should create boolean field $name", function(testCase)
local result = boolean_field(testCase.name, testCase.required, testCase.default)
expect(result.type).toBe("boolean")
expect(result.name).toBe(testCase.name)
expect(result.required).toBe(testCase.expectedRequired)
end)
end)
describe("array_field", function()
it.each({
{ name = "tags", itemType = "string", required = nil, expectedRequired = false },
{ name = "ids", itemType = "number", required = true, expectedRequired = true },
})("should create array field $name of $itemType", function(testCase)
local result = array_field(testCase.name, testCase.itemType, testCase.required)
expect(result.type).toBe("array")
expect(result.name).toBe(testCase.name)
expect(result.items.type).toBe(testCase.itemType)
expect(result.required).toBe(testCase.expectedRequired)
end)
end)
end)

View File

@@ -0,0 +1,69 @@
-- Relations tests for schema_editor package
-- Tests relationship definitions
local relations = require("relations")
describe("Schema Relations", function()
describe("constants", function()
it("should have relation type constants", function()
expect(relations.ONE_TO_ONE).toBe("one_to_one")
expect(relations.ONE_TO_MANY).toBe("one_to_many")
expect(relations.MANY_TO_MANY).toBe("many_to_many")
end)
end)
describe("define", function()
it.each({
{ type = "one_to_one", cascade = nil, expectedCascade = false },
{ type = "one_to_many", cascade = true, expectedCascade = true },
{ type = "many_to_many", cascade = false, expectedCascade = false },
})("should define $type relationship with cascade=$expectedCascade", function(testCase)
local from = { table = "users", field = "id" }
local to = { table = "profiles", field = "user_id" }
local options = testCase.cascade ~= nil and { cascade = testCase.cascade } or nil
local result = relations.define(testCase.type, from, to, options)
expect(result.type).toBe(testCase.type)
expect(result.from_table).toBe("users")
expect(result.from_field).toBe("id")
expect(result.to_table).toBe("profiles")
expect(result.to_field).toBe("user_id")
expect(result.cascade).toBe(testCase.expectedCascade)
end)
end)
describe("has_one", function()
it("should create one-to-one relationship", function()
local from = { table = "users", field = "id" }
local to = { table = "profiles", field = "user_id" }
local result = relations.has_one(from, to)
expect(result.type).toBe("one_to_one")
expect(result.from_table).toBe("users")
expect(result.to_table).toBe("profiles")
end)
end)
describe("has_many", function()
it("should create one-to-many relationship", function()
local from = { table = "users", field = "id" }
local to = { table = "posts", field = "author_id" }
local result = relations.has_many(from, to)
expect(result.type).toBe("one_to_many")
expect(result.from_table).toBe("users")
expect(result.to_table).toBe("posts")
end)
end)
describe("belongs_to_many", function()
it("should create many-to-many relationship with pivot", function()
local from = { table = "users", field = "id" }
local to = { table = "roles", field = "id" }
local result = relations.belongs_to_many(from, to, "user_roles")
expect(result.type).toBe("many_to_many")
expect(result.pivot_table).toBe("user_roles")
end)
end)
end)

View File

@@ -0,0 +1,61 @@
-- Tables tests for schema_editor package
-- Tests table CRUD actions
local tables = require("tables")
describe("Schema Tables", function()
describe("create", function()
it.each({
{ name = "users", fields = nil, expectedFields = 0, desc = "without fields" },
{ name = "posts", fields = {}, expectedFields = 0, desc = "with empty fields" },
})("should create table $desc", function(testCase)
local result = tables.create(testCase.name, testCase.fields)
expect(result.action).toBe("create_table")
expect(result.name).toBe(testCase.name)
expect(#result.fields).toBe(testCase.expectedFields)
end)
it("should create table with fields", function()
local fields = {
{ type = "string", name = "email" },
{ type = "number", name = "age" }
}
local result = tables.create("users", fields)
expect(result.action).toBe("create_table")
expect(#result.fields).toBe(2)
expect(result.fields[1].name).toBe("email")
end)
end)
describe("render", function()
it("should render table editor component", function()
local table_def = {
name = "products",
fields = { { type = "string", name = "title" } }
}
local result = tables.render(table_def)
expect(result.type).toBe("table_editor")
expect(result.props.name).toBe("products")
expect(#result.props.fields).toBe(1)
end)
end)
describe("add_field", function()
it("should create add field action", function()
local field = { type = "string", name = "description" }
local result = tables.add_field("products", field)
expect(result.action).toBe("add_field")
expect(result.table).toBe("products")
expect(result.field.name).toBe("description")
end)
end)
describe("remove_field", function()
it("should create remove field action", function()
local result = tables.remove_field("products", "description")
expect(result.action).toBe("remove_field")
expect(result.table).toBe("products")
expect(result.field).toBe("description")
end)
end)
end)