• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MushroomObserver / mushroom-observer / 24457854395

15 Apr 2026 01:40PM UTC coverage: 96.266% (+0.2%) from 96.092%
24457854395

Pull #4096

github

web-flow
Merge b9ffe1729 into be6e1e402
Pull Request #4096: Opt into CodeQL file coverage on PRs via advanced setup workflow

37046 of 38483 relevant lines covered (96.27%)

666.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.7
/app/controllers/api2_controller.rb
1
# frozen_string_literal: true
2

3
#
4
#  = API2 Controller
5
#
6
#  This controller handles the JSON and XML interfaces
7
#
8
#  == Actions
9
#
10
#  observations, etc.::   Entry point for REST requests.
11
#
12
################################################################################
13
#
14
class API2Controller < ApplicationController
4✔
15
  require("xmlrpc/client")
4✔
16
  require("api2")
4✔
17

18
  disable_filters
4✔
19

20
  # wrapped parameters break JSON requests in the unit tests.
21
  wrap_parameters false
4✔
22

23
  # Standard entry point for REST requests.
24
  def api_keys
4✔
25
    rest_query(:api_key)
2✔
26
  end
27

28
  def collection_numbers
4✔
29
    rest_query(:collection_number)
6✔
30
  end
31

32
  def comments
4✔
33
    rest_query(:comment)
6✔
34
  end
35

36
  def external_links
4✔
37
    rest_query(:external_link)
6✔
38
  end
39

40
  def external_sites
4✔
41
    rest_query(:external_site)
6✔
42
  end
43

44
  def field_slips
4✔
45
    rest_query(:field_slip)
10✔
46
  end
47

48
  def herbaria
4✔
49
    rest_query(:herbarium)
6✔
50
  end
51

52
  def herbarium_records
4✔
53
    rest_query(:herbarium_record)
6✔
54
  end
55

56
  def images
4✔
57
    rest_query(:image)
11✔
58
  end
59

60
  def locations
4✔
61
    rest_query(:location)
7✔
62
  end
63

64
  def location_descriptions
4✔
65
    rest_query(:location_description)
6✔
66
  end
67

68
  def names
4✔
69
    rest_query(:name)
7✔
70
  end
71

72
  def name_descriptions
4✔
73
    rest_query(:name_description)
6✔
74
  end
75

76
  def observations
4✔
77
    rest_query(:observation)
30✔
78
  end
79

80
  def occurrences
4✔
81
    rest_query(:occurrence)
6✔
82
  end
83

84
  def projects
4✔
85
    rest_query(:project)
6✔
86
  end
87

88
  def sequences
4✔
89
    rest_query(:sequence)
7✔
90
  end
91

92
  def species_lists
4✔
93
    rest_query(:species_list)
6✔
94
  end
95

96
  def users
4✔
97
    rest_query(:user)
7✔
98
  end
99

100
  ##############################################################################
101

102
  private
4✔
103

104
  def rest_query(type)
4✔
105
    @start_time = Time.zone.now
147✔
106
    args = params_to_api_args(type)
147✔
107

108
    if request.method == "POST"
147✔
109
      if args[:upload].present?
13✔
110
        args[:upload] = upload_from_multipart_form_data(args[:upload])
2✔
111
        logger.warn("API UPLOAD: #{args[:upload].inspect}")
2✔
112
      elsif is_request_body_an_upload?
11✔
113
        args[:upload] = upload_from_request_body
2✔
114
      end
115
      # Special exception to let caller who creates new user to see that user's
116
      # new API keys.  Otherwise there is no way to get that info via the API.
117
      @show_api_keys_for_new_user = true if type == :user
13✔
118
    end
119

120
    render_api_results(args)
147✔
121
  end
122

123
  # Massage params hash to proper args hash for api
124
  def params_to_api_args(type)
4✔
125
    args = params.to_unsafe_h.symbolize_keys.except(:controller)
147✔
126
    args[:method] = request.method
147✔
127
    args[:action] = type
147✔
128
    args.delete(:format)
147✔
129
    args
147✔
130
  end
131

132
  def upload_from_multipart_form_data(data)
4✔
133
    API2::Upload.new(data: data)
2✔
134
  end
135

136
  def is_request_body_an_upload?
4✔
137
    request.content_length.positive? &&
11✔
138
      request.media_type.present? &&
139
      request.media_type != "application/x-www-form-urlencoded" &&
140
      request.media_type != "multipart/form-data" &&
141
      request.body.present?
142
  end
143

144
  def upload_from_request_body
4✔
145
    API2::Upload.new(
2✔
146
      data: request.body,
147
      length: request.content_length,
148
      content_type: request.media_type,
149
      checksum: request.headers["CONTENT_MD5"].to_s
150
    )
151
  end
152

153
  def render_api_results(args)
4✔
154
    @user = user_from_key(args[:api_key])
147✔
155
    @api = API2.execute(args)
147✔
156
    do_render
147✔
157
  rescue StandardError => e
158
    @api ||= API2.new
×
159
    @api.errors << API2::RenderFailed.new(e)
×
160
    do_render
×
161
  end
162

163
  def user_from_key(key)
4✔
164
    APIKey.find_by(key: key)&.user
147✔
165
  end
166

167
  def do_render
4✔
168
    set_cors_headers
147✔
169
    request.format = "json" if request.format == "html"
147✔
170
    respond_to do |format|
147✔
171
      format.xml  { do_render_xml  }
217✔
172
      format.json { do_render_json }
224✔
173
    end
174
  end
175

176
  def do_render_xml
4✔
177
    render(layout: false, template: "/api2/results")
70✔
178
  end
179

180
  def do_render_json
4✔
181
    render(layout: false, template: "/api2/results")
77✔
182
  end
183

184
  def set_cors_headers
4✔
185
    return unless request.method == "GET"
147✔
186

187
    response.set_header("Access-Control-Allow-Origin", "*")
134✔
188
    response.set_header("Access-Control-Allow-Headers",
134✔
189
                        "Origin, X-Requested-With, Content-Type, Accept")
190
    response.set_header("Access-Control-Allow-Methods", "GET")
134✔
191
  end
192
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc