require "json" require "./handler" module Guff module APIMethods API = { "post": { "get_posts": { text: "Get posts matching query.", args: { "q": { text: "Search string.", type: :text, required: false, default: "", }, "page": { text: "Page number", type: :int, required: false, default: "1", }, "tags": { text: "Comma-separated list of tags (union)", type: :tag_list, required: false, default: "", }, "sort": { text: "Sort order of results", type: :sort, required: false, default: "date,desc", }, "cols": { text: "Column list", type: :text, required: false, }, }, }, "add_post": { text: "Create new post.", args: { "name": { text: "Post title.", type: :text, required: true, }, "slug": { text: "Post slug.", type: :slug, required: true, }, "body": { text: "Post body.", type: :text, required: true, }, "tags": { text: "Post tags.", type: :tag_list, required: false, default: "", }, }, }, "remove_posts": { text: "Remove existing posts.", args: { "post_ids": { text: "Post IDs.", type: :int_list, required: false, default: "", }, "tags": { text: "Post tags.", type: :tag_list, required: false, default: "", }, }, }, "set_tags": { text: "Set tags for posts.", args: { "post_ids": { text: "Post IDs.", type: :int_list, required: true, }, }, }, }, "dir": { "add": { text: "Create new directory", args: { "path": { text: "Path to new directory", type: :path, required: true, }, }, }, "remove": { text: "Remove directory", args: { "path": { text: "Path to existing directory", type: :path, required: true, }, }, }, }, "file": { "add": { text: "Upload new file", args: { "path": { text: "Destination file path", type: :path, required: true, }, }, }, "remove": { text: "Remove existing file", args: { "path": { text: "Destination file path", type: :path, required: true, }, }, }, }, "tag": { "get_tags": { text: "Get list of tags", }, "remove_tags": { text: "Remove set of tags", args: { "tags": { text: "Tags to remove.", type: :tag_list, required: false, default: "", }, }, }, }, "site": { "add_site": { text: "Create a new site", args: { "name": { text: "Name of this site", type: :text, required: true, }, "default": { text: "Is this the default site?", type: :bool, required: false, default: "f", }, "domains": { text: "Array of domain matches.", type: :domain_list, required: true, }, }, }, "remove_sites": { text: "Remove one or more existing sites", args: { "site_ids": { text: "IDs of sites to remove", type: :int_list, required: true, }, }, }, "set_default": { text: "Set new default site", args: { "site_id": { text: "ID of new default site", type: :int, required: true, }, }, }, "set_domains": { text: "Set domains for given site", args: { "site_id": { text: "ID of site", type: :int, required: true, }, "domains": { text: "Domains for this site.", type: :domain_list, required: true, }, }, }, }, "test": { "version": { text: "Get version", }, "get_posts": { text: "Test get posts", }, "error": { text: "Test error response", }, } } TYPE_CHECKS = { text: /.*/, slug: /^[a-z0-9\.-]+$/, int: /^\d+$/, int_list: /^\d+(?:,\d+)*$/, tag_list: /^(?:[a-z0-9_\/ -]+(?:,[a-z0-9_\/ -]+)*|)$/, sort: /^[a-z0-9_]+,(?:asc|desc)$/, # FIXME: lock this down more path: /^[^\/].*[^\/]$/, } private def get_method_args( params : HTTP::Params, namespace : String, method : String ) return {} of String => String unless ( API[namespace]? && API[namespace][method] && API[namespace][method][:args]? ) # get method args args = API[namespace][method][:args] as \ Hash(String, Hash(Symbol, String | Symbol | Bool)) args.keys.reduce({} of String => String) do |r, arg_name| arg_data = args[arg_name] as Hash(Symbol, String|Symbol|Bool) # check for required parameter if arg_data[:required] && !params.has_key?(arg_name) raise "missing required parameter: %s" % [arg_name] end # get value val = if arg_data[:required] params.fetch(arg_name) # elsif arg_data.has_key?(:default) elsif arg_data[:default]? params.fetch(arg_name, arg_data[:default] as String) else nil end if val # check value if !TYPE_CHECKS[arg_data[:type]].match(val) raise "invalid parameter format: %s" % [arg_name] end # add value to result r[arg_name] = val end # return result r end end ################ # post methods # ################ NO_TAGS = [] of Array(String) private def do_post_get_posts( context : HTTP::Server::Context, args : Hash(String, String) ) @models.post.get_posts( site_id: get_site(context), q: args["q"]? || "", tags: get_posts_tags(args["tags"]), page: args.has_key?("page") ? args["page"].to_i : 1, ).to_json end private def do_post_add_post( context : HTTP::Server::Context, args : Hash(String, String) ) post_id = @models.post.add_post( site_id: get_site(context), slug: args["slug"], name: args["name"], body: args["name"], tags: get_tags(args["tags"]?), ) # return json { post_id: post_id }.to_json end private def do_post_remove_posts( context : HTTP::Server::Context, args : Hash(String, String) ) @models.post.remove_posts( site_id: get_site(context), post_ids: args["post_ids"].split(',').map { |post_id| post_id.to_s.to_i }, ) { ok: true }.to_json end private def do_post_set_tags( context : HTTP::Server::Context, args : Hash(String, String) ) @models.post.set_tags( site_id: get_site(context), post_id: (args["post_id"] as String).to_i, tags: get_tags(args["tags"]?), ) { ok: true}.to_json end ############### # dir methods # ############### private def do_dir_add( context : HTTP::Server::Context, args : Hash(String, String) ) # TODO {ok: true}.to_json end private def do_dir_remove( context : HTTP::Server::Context, args : Hash(String, String) ) # TODO {ok: true}.to_json end ################ # file methods # ################ private def do_file_add( context : HTTP::Server::Context, args : Hash(String, String) ) # TODO {ok: true}.to_json end private def do_file_remove( context : HTTP::Server::Context, args : Hash(String, String) ) # TODO {ok: true}.to_json end ############### # tag methods # ############### private def do_tag_get_tags( context : HTTP::Server::Context, args : Hash(String, String) ) [{foo: "bar"}, {foo: "asdf"}].to_json end private def do_tag_remove_tags( context : HTTP::Server::Context, args : Hash(String, String) ) true.to_json end ################ # site methods # ################ private def do_site_add_site( context : HTTP::Server::Context, args : Hash(String, String) ) # TODO {ok: true}.to_json end private def do_site_remove_sites( context : HTTP::Server::Context, args : Hash(String, String) ) # TODO {ok: true}.to_json end private def do_site_set_default( context : HTTP::Server::Context, args : Hash(String, String) ) # TODO {ok: true}.to_json end private def do_site_set_domains( context : HTTP::Server::Context, args : Hash(String, String) ) # TODO {ok: true}.to_json end ################ # test methods # ################ private def do_test_version( context : HTTP::Server::Context, args : Hash(String, String) ) {version: Guff::VERSION}.to_json end private def do_test_get_posts( context : HTTP::Server::Context, args : Hash(String, String) ) [{foo: "bar"}, {foo: "asdf"}].to_json end private def do_test_error( context : HTTP::Server::Context, args : Hash(String, String) ) raise "some random error" end ################### # utility methods # ################### private def get_site(context : HTTP::Server::Context) : Int? @models.site.to_site(context.request.headers["host"]?) end private def get_tags( s : String? ) : Array(String) if s && s.size > 0 Array(String).from_json(s) else [] of String end end private def get_posts_tags( s : String? ) : Array(Array(String)) if s && s.size > 0 Array(Array(String)).from_json(s) else [] of Array(String) end end end end