diff --git a/packages/shared/types/config.lua b/packages/shared/types/config.lua new file mode 100644 index 000000000..38a8c2a48 --- /dev/null +++ b/packages/shared/types/config.lua @@ -0,0 +1,164 @@ +---@class PackageConfig +---@field enabled boolean Whether the package is enabled +---@field required_level number Minimum permission level required to access +---@field features table Feature flags for the package +---@field database_enabled boolean Whether database access is enabled +---@field components table Component-specific configuration + +---@class ComponentConfig +---@field enabled boolean Whether the component is enabled +---@field required_level number|nil Minimum permission level (inherits from package if nil) +---@field props table|nil Default props for the component + +---@class FeatureFlag +---@field name string Feature flag name +---@field enabled boolean Whether the feature is enabled +---@field description string Feature description +---@field required_level number Minimum permission level to toggle + +---@class DatabaseConfig +---@field enabled boolean Whether database is enabled +---@field connection string|nil Database connection string +---@field pool_size number|nil Connection pool size +---@field timeout number|nil Query timeout in milliseconds + +local M = {} + +---Create default package configuration +---@param package_name string The package name +---@param required_level number|nil Minimum permission level (default: 1 - USER) +---@return PackageConfig config Default package configuration +function M.create_package_config(package_name, required_level) + return { + enabled = true, + required_level = required_level or 1, + features = {}, + database_enabled = false, + components = {} + } +end + +---Check if a package is enabled +---@param config PackageConfig The package configuration +---@return boolean enabled True if package is enabled +function M.is_package_enabled(config) + return config.enabled == true +end + +---Check if a component is enabled +---@param package_config PackageConfig The package configuration +---@param component_id string The component ID +---@return boolean enabled True if component is enabled +function M.is_component_enabled(package_config, component_id) + if not M.is_package_enabled(package_config) then + return false + end + + local component_config = package_config.components[component_id] + if not component_config then + return true -- Components enabled by default if no specific config + end + + return component_config.enabled ~= false +end + +---Get required permission level for a component +---@param package_config PackageConfig The package configuration +---@param component_id string The component ID +---@return number required_level The required permission level +function M.get_component_level(package_config, component_id) + local component_config = package_config.components[component_id] + if component_config and component_config.required_level then + return component_config.required_level + end + return package_config.required_level +end + +---Check if a feature is enabled +---@param config PackageConfig The package configuration +---@param feature_name string The feature name +---@return boolean enabled True if feature is enabled +function M.is_feature_enabled(config, feature_name) + return config.features[feature_name] == true +end + +---Enable or disable a feature +---@param config PackageConfig The package configuration +---@param feature_name string The feature name +---@param enabled boolean Whether to enable the feature +---@return PackageConfig config Updated configuration +function M.set_feature(config, feature_name, enabled) + config.features[feature_name] = enabled + return config +end + +---Enable or disable database access +---@param config PackageConfig The package configuration +---@param enabled boolean Whether to enable database access +---@return PackageConfig config Updated configuration +function M.set_database_enabled(config, enabled) + config.database_enabled = enabled + return config +end + +---Merge two configurations (deep merge) +---@param base PackageConfig Base configuration +---@param override PackageConfig Override configuration +---@return PackageConfig merged Merged configuration +function M.merge_config(base, override) + local merged = {} + + -- Copy base + for k, v in pairs(base) do + merged[k] = v + end + + -- Override values + for k, v in pairs(override) do + if type(v) == "table" and type(merged[k]) == "table" then + merged[k] = M.merge_config(merged[k], v) + else + merged[k] = v + end + end + + return merged +end + +---Validate package configuration +---@param config table The configuration to validate +---@return boolean valid True if configuration is valid +---@return string|nil error Error message if invalid +function M.validate_config(config) + if type(config) ~= "table" then + return false, "Configuration must be a table" + end + + if type(config.enabled) ~= "boolean" then + return false, "config.enabled must be a boolean" + end + + if type(config.required_level) ~= "number" then + return false, "config.required_level must be a number" + end + + if config.required_level < 0 or config.required_level > 5 then + return false, "config.required_level must be between 0 and 5" + end + + if type(config.features) ~= "table" then + return false, "config.features must be a table" + end + + if type(config.database_enabled) ~= "boolean" then + return false, "config.database_enabled must be a boolean" + end + + if type(config.components) ~= "table" then + return false, "config.components must be a table" + end + + return true, nil +end + +return M diff --git a/packages/shared/types/permissions.lua b/packages/shared/types/permissions.lua new file mode 100644 index 000000000..588587f06 --- /dev/null +++ b/packages/shared/types/permissions.lua @@ -0,0 +1,173 @@ +---@class PermissionLevel +---@field PUBLIC number 0 - Public (no login required) +---@field USER number 1 - Logged in users +---@field MODERATOR number 2 - Moderators +---@field ADMIN number 3 - Administrators +---@field GOD number 4 - God mode (full access) +---@field SUPERGOD number 5 - Supergod (system level) + +---@class PermissionLevels +---@field PUBLIC number +---@field USER number +---@field MODERATOR number +---@field ADMIN number +---@field GOD number +---@field SUPERGOD number + +---@type PermissionLevels +local LEVELS = { + PUBLIC = 0, + USER = 1, + MODERATOR = 2, + ADMIN = 3, + GOD = 4, + SUPERGOD = 5 +} + +---@class PermissionContext +---@field user_id string|nil The user's ID +---@field username string|nil The user's username +---@field level number The user's permission level +---@field tenant_id string|nil The tenant ID +---@field features table|nil Enabled features + +---@class PermissionCheck +---@field required_level number Minimum permission level required +---@field feature string|nil Feature flag that must be enabled +---@field tenant_specific boolean|nil Whether permission is tenant-specific + +---@class PermissionResult +---@field allowed boolean Whether the action is allowed +---@field reason string|nil Reason for denial + +local M = {} + +---Check if a user has the required permission level +---@param ctx PermissionContext The user's permission context +---@param required_level number The required permission level +---@return boolean allowed True if the user has sufficient permissions +function M.has_level(ctx, required_level) + return ctx.level >= required_level +end + +---Check if a user can access a feature +---@param ctx PermissionContext The user's permission context +---@param check PermissionCheck The permission check configuration +---@return PermissionResult result The permission check result +function M.can_access(ctx, check) + -- Check permission level + if ctx.level < check.required_level then + return { + allowed = false, + reason = string.format( + "Requires level %d (%s), user has level %d", + check.required_level, + M.level_name(check.required_level), + ctx.level + ) + } + end + + -- Check feature flag if specified + if check.feature and ctx.features then + if not ctx.features[check.feature] then + return { + allowed = false, + reason = string.format("Feature '%s' is not enabled", check.feature) + } + end + end + + -- Check tenant-specific permissions + if check.tenant_specific and not ctx.tenant_id then + return { + allowed = false, + reason = "Tenant-specific operation requires valid tenant_id" + } + end + + return { allowed = true } +end + +---Get the name of a permission level +---@param level number The permission level +---@return string name The level name +function M.level_name(level) + local names = { + [0] = "PUBLIC", + [1] = "USER", + [2] = "MODERATOR", + [3] = "ADMIN", + [4] = "GOD", + [5] = "SUPERGOD" + } + return names[level] or "UNKNOWN" +end + +---Create a permission context for an anonymous user +---@return PermissionContext ctx Anonymous permission context +function M.anonymous() + return { + level = LEVELS.PUBLIC, + features = {} + } +end + +---Create a permission context for a logged-in user +---@param user_id string The user's ID +---@param username string The user's username +---@param level number The user's permission level +---@param tenant_id string|nil The tenant ID +---@param features table|nil Enabled features +---@return PermissionContext ctx User permission context +function M.user_context(user_id, username, level, tenant_id, features) + return { + user_id = user_id, + username = username, + level = level, + tenant_id = tenant_id, + features = features or {} + } +end + +---Check if user is admin or higher +---@param ctx PermissionContext The user's permission context +---@return boolean is_admin True if user is admin or higher +function M.is_admin(ctx) + return ctx.level >= LEVELS.ADMIN +end + +---Check if user is moderator or higher +---@param ctx PermissionContext The user's permission context +---@return boolean is_moderator True if user is moderator or higher +function M.is_moderator(ctx) + return ctx.level >= LEVELS.MODERATOR +end + +---Check if user is god mode or higher +---@param ctx PermissionContext The user's permission context +---@return boolean is_god True if user is god mode or higher +function M.is_god(ctx) + return ctx.level >= LEVELS.GOD +end + +---Filter a list based on permission requirements +---@param items table[] List of items to filter +---@param ctx PermissionContext User permission context +---@param get_required_level fun(item: table): number Function to get required level for item +---@return table[] filtered Filtered list of items user can access +function M.filter_by_permission(items, ctx, get_required_level) + local filtered = {} + for _, item in ipairs(items) do + local required = get_required_level(item) + if M.has_level(ctx, required) then + table.insert(filtered, item) + end + end + return filtered +end + +-- Export permission levels +M.LEVELS = LEVELS + +return M