-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi_openstates.php
More file actions
129 lines (118 loc) · 5.45 KB
/
api_openstates.php
File metadata and controls
129 lines (118 loc) · 5.45 KB
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
<?php
/**
* api_openstates.php — OpenStates v3 API proxy (state legislators, bills, votes).
*
* Ops:
* op=legislators state=<2-letter> page=1
* op=legislator id=<ocd-person/uuid>
* op=bills_by_subject state=<2-letter> subject=<string> session=<string>?
* op=bill id=<ocd-bill/uuid>
* op=bill_votes id=<ocd-bill/uuid>
* op=search_bills state=<2-letter> q=<keyword>
*
* Auth: OpenStates uses X-API-KEY header.
* Rate limit: 180 req/hr per IP (OpenStates free tier is ~500/day per key).
*/
require_once __DIR__ . '/api_keys.php';
fca_proxy_headers();
if (!fca_rate_ok('openstates', 180)) {
http_response_code(429);
echo json_encode(['ok'=>false,'error'=>'Rate limit: 180 requests per hour per IP.']);
exit;
}
$key = fca_load_key('OPENSTATES_API_KEY');
if (!$key) {
http_response_code(503);
echo json_encode(['ok'=>false,'error'=>'OpenStates API key not configured on server.',
'setup'=>'Add OPENSTATES_API_KEY=... to /etc/fca/api_keys.env']);
exit;
}
$op = preg_replace('/[^a-z_]/', '', (string)($_GET['op'] ?? ''));
$ALLOWED = ['legislators','legislator','bills_by_subject','bill','bill_votes','search_bills'];
if (!in_array($op, $ALLOWED, true)) {
http_response_code(400);
echo json_encode(['ok'=>false,'error'=>'Unsupported op. Allowed: '.implode(', ',$ALLOWED)]);
exit;
}
$state = preg_replace('/[^a-z]/', '', strtolower(substr((string)($_GET['state'] ?? ''), 0, 2)));
$id = preg_replace('/[^a-z0-9\-\/]/', '', substr((string)($_GET['id'] ?? ''), 0, 100));
$q = preg_replace('/[^a-zA-Z0-9 .,\'\-]/u', '', substr((string)($_GET['q'] ?? ''), 0, 200));
$subject = preg_replace('/[^a-zA-Z0-9 \-]/', '', substr((string)($_GET['subject'] ?? ''), 0, 60));
$session = preg_replace('/[^A-Za-z0-9\-]/', '', substr((string)($_GET['session'] ?? ''), 0, 30));
$page = max(1, min(20, (int)($_GET['page'] ?? 1)));
$perPage = min(50, max(1, (int)($_GET['per_page'] ?? 20)));
$base = 'https://v3.openstates.org';
$url = null;
// OpenStates v3 `include` params must be repeated (include=a&include=b), not
// comma-separated. http_build_query comma-concatenates, which OpenStates rejects
// with 422. Helper builds the `&include=x` suffix manually.
$mk_includes = function(array $inc) {
if (!$inc) return '';
$parts = [];
foreach ($inc as $v) { $parts[] = 'include=' . urlencode($v); }
return '&' . implode('&', $parts);
};
switch ($op) {
case 'legislators':
if ($state === '') { http_response_code(400); echo json_encode(['ok'=>false,'error'=>'state required']); exit; }
$params = [
'jurisdiction' => "ocd-jurisdiction/country:us/state:$state/government",
'per_page' => $perPage,
'page' => $page,
];
$url = $base . '/people?' . http_build_query($params) . $mk_includes(['offices','other_identifiers']);
break;
case 'legislator':
if ($id === '') { http_response_code(400); echo json_encode(['ok'=>false,'error'=>'id required']); exit; }
$url = $base . '/people?id=' . urlencode($id) . $mk_includes(['offices','other_identifiers','sources']);
break;
case 'bills_by_subject':
if ($state === '' || $subject === '') {
http_response_code(400); echo json_encode(['ok'=>false,'error'=>'state and subject required']); exit;
}
$params = [
'jurisdiction' => "ocd-jurisdiction/country:us/state:$state/government",
'subject' => $subject,
'per_page' => $perPage,
'page' => $page,
'sort' => 'updated_desc',
];
if ($session) $params['session'] = $session;
$url = $base . '/bills?' . http_build_query($params) . $mk_includes(['sponsorships','abstracts']);
break;
case 'search_bills':
if ($state === '' || $q === '') {
http_response_code(400); echo json_encode(['ok'=>false,'error'=>'state and q required']); exit;
}
$params = [
'jurisdiction' => "ocd-jurisdiction/country:us/state:$state/government",
'q' => $q,
'per_page' => $perPage,
'page' => $page,
'sort' => 'updated_desc',
];
$url = $base . '/bills?' . http_build_query($params);
break;
case 'bill':
if ($id === '') { http_response_code(400); echo json_encode(['ok'=>false,'error'=>'id required']); exit; }
$url = $base . '/bills?id=' . urlencode($id) . $mk_includes(['sponsorships','abstracts','actions','votes','documents','versions','sources']);
break;
case 'bill_votes':
if ($id === '') { http_response_code(400); echo json_encode(['ok'=>false,'error'=>'id required']); exit; }
$url = $base . '/bills?id=' . urlencode($id) . $mk_includes(['votes']);
break;
}
$cache_key = $op . '|' . md5($url);
$cached = fca_cache_get('openstates', $cache_key, 86400);
if ($cached !== null) {
echo json_encode(['ok'=>true,'source'=>'openstates','op'=>$op,'data'=>json_decode($cached,true),'cached'=>true]);
exit;
}
$resp = fca_http_get_json($url, ['X-API-KEY'=>$key], 25);
if (!$resp['ok']) {
http_response_code($resp['status'] ?: 502);
echo json_encode(['ok'=>false,'source'=>'openstates','op'=>$op,'status'=>$resp['status'],'error'=>$resp['error']?:'upstream error']);
exit;
}
fca_cache_put('openstates', $cache_key, json_encode($resp['body']));
echo json_encode(['ok'=>true,'source'=>'openstates','op'=>$op,'data'=>$resp['body'],'cached'=>false]);