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