UNPKG

@inkwell.ar/sdk

Version:

SDK for interacting with the Inkwell Blog CRUD AO process using aoconnect for deployment and interactions

2 lines 19.3 kB
export declare const BLOG_CONTRACT = "local json = require('json')\nlocal AccessControl = require('access_control')\n\nName = \"Inkwell Blog\"\nAuthor = \"@7i7o\"\nDetails = {\n title = \"Blog Title\",\n description = \"Blog Description\",\n logo = \"\"\n}\n\n-- Initialize AccessControl\nAccessControl.init(Owner, Owner, false)\nAccessControl.create_role(Owner, AccessControl.ROLES.EDITOR, AccessControl.ROLES.DEFAULT_ADMIN)\nAccessControl.grant_role(Owner, AccessControl.ROLES.EDITOR, Owner)\n\n-- Registry integration\nlocal RegistryProcess = \"p2un0gLaXvUFc8Sm1ArASEPWRNVGzrYkBSr_KHgeLRg\"\n\n-- Posts storage\nPosts = Posts or {}\nNext_id = Next_id or 1\n\n-- Initialize next_id in case posts already exist\nlocal function initialize_next_id()\n local max_id = 0\n for id, _ in pairs(Posts) do\n if type(id) == \"number\" and id > max_id then\n max_id = id\n end\n end\n Next_id = max_id + 1\nend\ninitialize_next_id()\n\n-- Helper function to validate blog details\nlocal function validate_blog_details(data)\n if not data or type(data) ~= \"table\" then\n return false, \"Data is required and must be a table\"\n end\n\n if data.title and type(data.title) ~= \"string\" then\n return false, \"Field title must be a non-empty string\"\n end\n\n if data.description and type(data.description) ~= \"string\" then\n return false, \"Field description and must be a non-empty string\"\n end\n\n if data.logo and type(data.logo) ~= \"string\" then\n return false, \"Field body must be a string if provided\"\n end\n\n if (not data.title or data.title == \"\") and (not data.description or data.description == \"\") and (not data.logo or data.logo == \"\") then\n return false, \"At least one field is required\"\n end\n\n return true, nil\nend\n\n-- Helper function to validate posts data\nlocal function validate_post(data)\n if not data or type(data) ~= \"table\" then\n return false, \"Data is required and must be a table\"\n end\n\n if not data.title or type(data.title) ~= \"string\" or data.title == \"\" then\n return false, \"Field title is required and must be a non-empty string\"\n end\n\n if not data.description or type(data.description) ~= \"string\" or data.description == \"\" then\n return false, \"Field description is required and must be a non-empty string\"\n end\n\n if data.body and type(data.body) ~= \"string\" then\n return false, \"Field body must be a string if provided\"\n end\n\n if not data.published_at or type(data.published_at) ~= \"number\" then\n return false, \"Field published_at is required and must be a number\"\n end\n\n if not data.last_update or type(data.last_update) ~= \"number\" then\n return false, \"Field last_update is required and must be a number\"\n end\n\n if data.labels then\n if type(data.labels) ~= \"table\" then\n return false, \"Field labels must be a table if provided\"\n end\n for _, label in ipairs(data.labels) do\n if not label or type(label) ~= \"string\" or label == \"\" then\n return false, \"Fields within labels must all be strings if provided\"\n end\n end\n end\n\n if not data.authors or type(data.authors) ~= \"table\" or #data.authors == 0 then\n return false, \"Field authors is required, must be a table and should have at least 1 item\"\n else\n for _, author in ipairs(data.authors) do\n if not author or type(author) ~= \"string\" or author == \"\" then\n return false, \"Fields within authors are required and must all be non-empty strings\"\n end\n end\n end\n\n return true, nil\nend\n\n-- Helper function to deep copy an array\nlocal function copy_array(arr)\n if not arr or type(arr) ~= \"table\" then return {} end\n local result = {}\n for _, item in ipairs(arr) do\n table.insert(result, item)\n end\n return result\nend\n\n-- Atomic ID generation\nlocal function get_next_id()\n local id = Next_id\n Next_id = Next_id + 1\n return id\nend\n\n-- Create a new post\nlocal function create_post(data)\n local is_valid, error = validate_post(data)\n if not is_valid then\n return nil, error\n end\n\n local post = {\n title = data.title,\n description = data.description,\n body = data.body,\n published_at = data.published_at,\n last_update = data.last_update,\n labels = copy_array(data.labels),\n authors = copy_array(data.authors)\n }\n\n post.id = get_next_id()\n Posts[post.id] = post\n\n return post, nil\nend\n\n-- Get all posts\nlocal function get_posts(is_ordered)\n local result = {}\n for id, post in pairs(Posts) do\n table.insert(result, post)\n end\n\n -- Sort by published_at (newest first)\n if is_ordered then\n table.sort(result, function(a, b)\n return a.published_at > b.published_at\n end)\n end\n\n return result\nend\n\n-- Get post by ID\nlocal function get_post_by_id(id)\n if type(id) ~= \"number\" then\n return nil, \"ID must be a number\"\n end\n\n local post = Posts[id]\n if not post then\n return nil, \"Post not found\"\n end\n\n return post, nil\nend\n\n-- Update a post\nlocal function update_post(id, data)\n if type(id) ~= \"number\" then\n return nil, \"ID must be a number\"\n end\n\n if not Posts[id] then\n return nil, \"Post not found\"\n end\n\n local is_valid, error = validate_post(data)\n if not is_valid then\n return nil, error\n end\n\n local post = Posts[id]\n post.title = data.title\n post.description = data.description\n post.body = data.body\n post.published_at = data.published_at\n post.last_update = data.last_update\n post.labels = copy_array(data.labels)\n post.authors = copy_array(data.authors)\n\n return post, nil\nend\n\n-- Delete a post\nlocal function delete_post(id)\n if type(id) ~= \"number\" then\n return nil, \"ID must be a number\"\n end\n\n if not Posts[id] then\n return nil, \"Post not found\"\n end\n\n local post = Posts[id]\n Posts[id] = nil\n\n return post, nil\nend\n\n-- Helper function to safely parse JSON\nlocal function safe_json_decode(data)\n if not data or type(data) ~= \"string\" then\n return nil, \"Invalid JSON data\"\n end\n\n local success, result = pcall(json.decode, data)\n if not success then\n return nil, \"Invalid JSON format: \" .. result\n end\n\n return result, nil\nend\n\n----------------------------------\n--- Message Helper\nlocal function reply_msg(msg, success, data_or_error)\n msg.reply({\n Data = json.encode({\n success = success,\n data = data_or_error\n })\n })\nend\n\n----------------------------------\n-- Helper function to handle errors in handlers\nlocal function safe_handler(handler_func)\n return function(msg)\n local success, result = pcall(handler_func, msg)\n if not success then\n reply_msg(msg, false, \"Internal server error: \" .. result)\n end\n end\nend\n\n----------------------------------\n-- Registry sync helpers\nlocal function sync_wallet_to_registry(wallet, roles)\n if not RegistryProcess or RegistryProcess == \"YOUR_REGISTRY_PROCESS_ID\" then\n return true, nil -- Skip if registry not configured\n end\n \n local data = {\n wallet = wallet,\n roles = roles\n }\n \n -- Send message to registry process\n ao.send({\n Target = RegistryProcess,\n Action = \"Register-Wallet-Permissions\",\n Data = json.encode(data)\n })\n \n return true, nil\nend\n\nlocal function remove_wallet_from_registry(wallet)\n if not RegistryProcess or RegistryProcess == \"YOUR_REGISTRY_PROCESS_ID\" then\n return true, nil -- Skip if registry not configured\n end\n \n local data = {\n wallet = wallet\n }\n \n -- Send message to registry process\n ao.send({\n Target = RegistryProcess,\n Action = \"Remove-Wallet-Permissions\",\n Data = json.encode(data)\n })\n \n return true, nil\nend\n\nlocal function get_wallet_roles_for_registry(wallet)\n local roles = {}\n \n -- Check for DEFAULT_ADMIN role\n local is_admin, _ = AccessControl.has_role(wallet, AccessControl.ROLES.DEFAULT_ADMIN)\n if is_admin then\n table.insert(roles, AccessControl.ROLES.DEFAULT_ADMIN)\n end\n \n -- Check for EDITOR role\n local is_editor, _ = AccessControl.has_role(wallet, AccessControl.ROLES.EDITOR)\n if is_editor then\n table.insert(roles, AccessControl.ROLES.EDITOR)\n end\n \n return roles\nend\n\n----------------------------------\n-- Role Change Helper\nlocal function update_roles(msg, role_action, role)\n local is_admin, err = AccessControl.only_role(msg.From, AccessControl.ROLES.DEFAULT_ADMIN)\n\n -- Only ADMINs can update roles\n if not is_admin then\n return false, err\n end\n\n local results = {}\n local global_success = true\n\n local data, error = safe_json_decode(msg.Data)\n if not data then\n return false, error\n end\n\n if not data.accounts or type(data.accounts) ~= \"table\" then\n return false, \"Field 'accounts' is required and must be an array\"\n end\n\n if #data.accounts == 0 then\n return false, \"Field 'accounts' must contain at least one account\"\n end\n\n for _, account in ipairs(data.accounts) do\n if type(account) ~= \"string\" or account == \"\" then\n table.insert(results, { account, false, AccessControl.ERROR_MESSAGES.INVALID_ACCOUNT })\n global_success = false\n else\n local success, err = role_action(msg.From, role, account)\n table.insert(results, { account, success, err })\n global_success = global_success and success\n \n -- Sync with registry after role change\n if success then\n local roles = get_wallet_roles_for_registry(account)\n if #roles > 0 then\n sync_wallet_to_registry(account, roles)\n else\n remove_wallet_from_registry(account)\n end\n end\n end\n end\n\n local success, result = pcall(json.encode, results)\n if not success then\n return false, \"JSON encoding results failed: \" .. result\n end\n\n return global_success, result\nend\n\n----------------------------------\n-- List Role Members\nlocal function get_role_members(msg, role)\n local results, error = AccessControl.get_role_members(role)\n if not results then\n return false, error\n end\n\n local success, result = pcall(json.encode, results)\n if not success then\n return false, \"JSON encoding results failed: \" .. result\n end\n\n return true, result\nend\n\n----------------------------------\n-- List User Roles\nlocal function get_user_roles(user_address)\n local results, error = AccessControl.get_user_roles(user_address)\n if not results then\n return false, error\n end\n\n local success, result = pcall(json.encode, results)\n if not success then\n return false, \"JSON encoding results failed: \" .. result\n end\n\n return true, result\nend\n\n----------------------------------\n-- Set Blog Details\nlocal function set_details(data)\n local success, error = validate_blog_details(data)\n\n if not success then\n return false, error\n end\n\n local new_details = {\n title = data.title or Details.title,\n description = data.description or Details.description,\n logo = data.logo or Details.logo\n }\n\n Details = new_details\n return true, new_details\nend\n\n----------------------------------\n--- Message handlers for AO process\n\n-- Public Handlers\nHandlers.add(\n \"Info\",\n Handlers.utils.hasMatchingTag(\"Action\", \"Info\"),\n safe_handler(function(msg)\n msg.reply({\n Name = Name,\n Author = Author,\n [\"Blog-Title\"] = Details.title,\n [\"Blog-Description\"] = Details.description,\n [\"Blog-Logo\"] = Details.logo,\n Data = json.encode({\n success = true,\n data = Details\n })\n })\n end)\n)\n\nHandlers.add(\n \"Get-All-Posts\",\n Handlers.utils.hasMatchingTag(\"Action\", \"Get-All-Posts\"),\n safe_handler(function(msg)\n local is_ordered = msg.Tags.Ordered == \"true\"\n local posts = get_posts(is_ordered)\n reply_msg(msg, true, posts)\n end)\n)\n\nHandlers.add(\n \"Get-Post\",\n Handlers.utils.hasMatchingTag(\"Action\", \"Get-Post\"),\n safe_handler(function(msg)\n local id = tonumber(msg.Tags.Id)\n local post, error = get_post_by_id(id)\n reply_msg(msg, post ~= nil, post or error)\n end)\n)\n\nHandlers.add(\n \"Get-User-Roles\",\n Handlers.utils.hasMatchingTag(\"Action\", \"Get-User-Roles\"),\n safe_handler(function(msg)\n local user_address = msg.Tags[\"User-Address\"]\n local success, data_or_error = get_user_roles(user_address)\n reply_msg(msg, success, data_or_error)\n end)\n)\n\n-- Editor only handlers\nHandlers.add(\n \"Create-Post\",\n Handlers.utils.hasMatchingTag(\"Action\", \"Create-Post\"),\n safe_handler(function(msg)\n local is_editor, error = AccessControl.only_role(msg.From, AccessControl.ROLES.EDITOR)\n local post = nil\n\n -- Only EDITORs can create posts\n if is_editor then\n local data, err = safe_json_decode(msg.Data)\n if not data then\n reply_msg(msg, false, err)\n return\n end\n post, error = create_post(data)\n end\n\n reply_msg(msg, post ~= nil, post or error)\n end)\n)\n\nHandlers.add(\n \"Update-Post\",\n Handlers.utils.hasMatchingTag(\"Action\", \"Update-Post\"),\n safe_handler(function(msg)\n local is_editor, error = AccessControl.only_role(msg.From, AccessControl.ROLES.EDITOR)\n local post = nil\n\n -- Only EDITORs can update posts\n if is_editor then\n local id = tonumber(msg.Tags.Id)\n local data, err = safe_json_decode(msg.Data)\n if not data then\n reply_msg(msg, false, err)\n return\n end\n post, error = update_post(id, data)\n end\n\n reply_msg(msg, post ~= nil, post or error)\n end)\n)\n\nHandlers.add(\n \"Delete-Post\",\n Handlers.utils.hasMatchingTag(\"Action\", \"Delete-Post\"),\n safe_handler(function(msg)\n local is_editor, error = AccessControl.only_role(msg.From, AccessControl.ROLES.EDITOR)\n local post = nil\n\n -- Only EDITORs can delete posts\n if is_editor then\n local id = tonumber(msg.Tags.Id)\n post, error = delete_post(id)\n end\n\n reply_msg(msg, post ~= nil, post or error)\n end)\n)\n\n-- Admin only handlers\nHandlers.add(\n \"Add-Editors\",\n Handlers.utils.hasMatchingTag(\"Action\", \"Add-Editors\"),\n safe_handler(function(msg)\n local success, result_or_error = update_roles(msg, AccessControl.grant_role, AccessControl.ROLES.EDITOR)\n reply_msg(msg, success, result_or_error)\n end)\n)\n\nHandlers.add(\n \"Remove-Editors\",\n Handlers.utils.hasMatchingTag(\"Action\", \"Remove-Editors\"),\n safe_handler(function(msg)\n local success, result_or_error = update_roles(msg, AccessControl.revoke_role, AccessControl.ROLES.EDITOR)\n reply_msg(msg, success, result_or_error)\n end)\n)\n\nHandlers.add(\n \"Add-Admins\",\n Handlers.utils.hasMatchingTag(\"Action\", \"Add-Admins\"),\n safe_handler(function(msg)\n local success, result_or_error = update_roles(msg, AccessControl.grant_role, AccessControl.ROLES.DEFAULT_ADMIN)\n reply_msg(msg, success, result_or_error)\n end)\n)\n\nHandlers.add(\n \"Remove-Admins\",\n Handlers.utils.hasMatchingTag(\"Action\", \"Remove-Admins\"),\n safe_handler(function(msg)\n local success, result_or_error = update_roles(msg, AccessControl.revoke_role, AccessControl.ROLES.DEFAULT_ADMIN)\n reply_msg(msg, success, result_or_error)\n end)\n)\n\nHandlers.add(\n \"Get-Editors\",\n Handlers.utils.hasMatchingTag(\"Action\", \"Get-Editors\"),\n safe_handler(function(msg)\n local success, result_or_error = get_role_members(msg, AccessControl.ROLES.EDITOR)\n reply_msg(msg, success, result_or_error)\n end)\n)\n\nHandlers.add(\n \"Get-Admins\",\n Handlers.utils.hasMatchingTag(\"Action\", \"Get-Admins\"),\n safe_handler(function(msg)\n local success, result_or_error = get_role_members(msg, AccessControl.ROLES.EDITOR)\n reply_msg(msg, success, result_or_error)\n end)\n)\n\nHandlers.add(\n \"Set-Blog-Details\",\n Handlers.utils.hasMatchingTag(\"Action\", \"Set-Blog-Details\"),\n safe_handler(function(msg)\n local is_admin, error = AccessControl.only_role(msg.From, AccessControl.ROLES.DEFAULT_ADMIN)\n\n if not is_admin then\n reply_msg(msg, false, error)\n return false, error\n end\n\n local data, err = safe_json_decode(msg.Data)\n if not data then\n reply_msg(msg, false, err)\n return false, err\n end\n\n local success, result_or_error = set_details(data)\n\n reply_msg(msg, success, result_or_error)\n end)\n)\n\nHandlers.add(\n \"Sync-With-Registry\",\n Handlers.utils.hasMatchingTag(\"Action\", \"Sync-With-Registry\"),\n safe_handler(function(msg)\n local is_admin, error = AccessControl.only_role(msg.From, AccessControl.ROLES.DEFAULT_ADMIN)\n\n if not is_admin then\n reply_msg(msg, false, error)\n return\n end\n\n local sync_results = {}\n \n -- Get all roles and sync them\n for _, role in pairs(AccessControl.ROLES) do\n local members, err = AccessControl.get_role_members(role)\n if members then\n for _, wallet in ipairs(members) do\n local roles = get_wallet_roles_for_registry(wallet)\n if #roles > 0 then\n sync_wallet_to_registry(wallet, roles)\n table.insert(sync_results, {\n wallet = wallet,\n roles = roles,\n synced = true\n })\n end\n end\n end\n end\n\n reply_msg(msg, true, {\n message = \"Sync completed\",\n synced_wallets = #sync_results,\n results = sync_results\n })\n end)\n)"; //# sourceMappingURL=blog-contract.d.ts.map