aboutsummaryrefslogtreecommitdiff
path: root/src/guff/handlers/api.cr
blob: 523f2f282e16cc9614790edd5450dc7661ae53a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
require "json"
require "../handler"
require "../api/*"
require "../views/html/api-docs"

private macro define_method_calls(hash)
  case namespace
  {% for namespace, methods in hash %}
    when "{{ namespace.id }}"
      case method
      {% for method in methods %}
        when "{{ method }}"
          params = case context.request.method
          when "GET"
            context.request.query_params
          when "POST"
            HTTP::Params.parse((context.request.body || "") as String)
          else
            raise "unsupported method"
          end

          do_{{ namespace.id }}_{{ method }}(context, get_method_args(
            params,
            "{{ namespace.id }}",
            "{{ method.id }}"
          )).to_json
      {% end %}
      else
        raise "unknown method"
      end
  {% end %}
  else
    raise "unknown namespace"
  end
end

class Guff::Handlers::APIHandler < Guff::Handler
  include API::Methods
  include API::ContentType
  include API::Util
  include API::PostAPI
  include API::DirAPI
  include API::FileAPI
  include API::TagAPI
  include API::SiteAPI
  include API::TestAPI

  PATH_RE = %r{
    ^/api

    (?:
      # method call
      (?:
        /
        (?<namespace>[a-z0-9_-]+)
        /
        (?<method>[a-z0-9_]+)
      )

      |

      # index.html
      /(?:index(?:\.html|)|)

      |

      # implicit index (no trailing slash)
    )

    $
  }mx

  def call(context : HTTP::Server::Context)
    if md = (context.request.path || "").match(PATH_RE)
      if md["namespace"]?
        # method call
        do_api_call(context, md["namespace"], md["method"])
      else
        # api index
        do_api_docs(context)
      end
    else
      call_next(context)
    end
  end

  private def do_api_call(
    context   : HTTP::Server::Context,
    namespace : String,
    method    : String
  )
    # default to success
    ok = true

    # method call
    json = begin
      #
      # macro expands to equivalent of the following for each
      # namespace and method:
      #
      #   args = get_method_args(context, namespace, method)
      #   send("do_#{namespace}_#{method}".intern, context, args)
      #
      define_method_calls({
        post: [
          get_posts,
          add_post,
          update_post,
          remove_posts,
          set_tags,
        ],

        dir: [
          add,
          remove,
        ],

        file: [
          add,
          remove,
        ],

        tag: [
          get_tags,
          remove_tags,
        ],

        site: [
          add_site,
          remove_sites,
          set_default,
          set_domains,
        ],

        test: [
          version,
          get_posts,
          error,
          get_users,
          set_user,
          add_user,
          edit_user,
          login,
        ],
      })
    rescue e
      # set error
      ok = false

      # log backtrace
      # FIXME
      puts e.inspect_with_backtrace

      # return error to user
      { "error": e.to_s }.to_json
    end

    # send type, code, and body
    context.response.content_type = get_content_type
    context.response.status_code = ok ? 200 : 400
    context.response << json
  end

  private def do_api_docs(context : HTTP::Server::Context)
    Guff::APIDocsHTMLView.run(context)
  end
end