Diff -- /var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/api.cgi
Diff

/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/api.cgi

added on local at 2026-07-01 12:34:01

Added
+511
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 421f1d780441
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1#!/usr/bin/perl
2
3##################################################################################################################################
4# ______ ____ ____ ______ ______ ____ ___ __ ___ ______ _ __ ____ ____ __ __ _ __ _____ ____
5# / ____// __ \ / __ \ / ____/ / ____// __ \ / | / |/ // ____/| | / // __ \ / __ \ / //_/ | | / /|__ / / __ \
6# / / / / / // / / // __/ / /_ / /_/ // /| | / /|_/ // __/ | | /| / // / / // /_/ // ,< | | / / /_ < / / / /
7# / /___ / /_/ // /_/ // /___ / __/ / _, _// ___ | / / / // /___ | |/ |/ // /_/ // _, _// /| | | |/ / ___/ /_ / /_/ /
8# \____/ \____//_____//_____/ /_/ /_/ |_|/_/ |_|/_/ /_//_____/ |__/|__/ \____//_/ |_|/_/ |_| |___/ /____/(_)\____/
9#
10# - Written by: Shawn Pickering
11# - shawn@shawnpickering.com
12##################################################################################################################################
13use strict;
14use lib '/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com';
15
16
17#############################################################
18## Variables
19#############################################################
20our $DBH;
21our $AUTH;
22
23
24
25############################################################
26# Modules
27############################################################
28$CGI::POST_MAX = 8 * 1024 * 1024;
29use CGI; my $query = new CGI; my $form = $query->Vars;
30use MODS::Login; my $login = new MODS::Login;
31use MODS::PTMatrix::Urls; my $getlist = new MODS::PTMatrix::Urls; my $url = $getlist->urls();
32use MODS::Template; my $tfile = new MODS::Template;
33use MODS::PTMatrix::Wrapper; my $load = new MODS::PTMatrix::Wrapper;
34use MODS::DBConnect; my $db = new MODS::DBConnect;
35use MODS::PTMatrix::Config; my $config = new MODS::PTMatrix::Config; my $database_name = $config->settings('database_name');
36use MODS::PTMatrix::PM;
37use MODS::PTMatrix::API; my $api = new MODS::PTMatrix::API;
38use Digest::SHA qw(sha256_hex);
39
40my %rawform = %$form;
41
42
43
44############################################################
45# Program Controls
46############################################################
47$|=1;
48my $entry = ($ENV{SCRIPT_NAME} || '') =~ m{/([^/]+)\.cgi$} ? $1 : 'api';
49
50if($entry eq 'api_keys'){
51 foreach my $line(keys %$form){
52 $form->{$line} =~ s/[^!-~\s]//g;
53 $form->{$line} = quotemeta("$form->{$line}");
54 }
55 my $userinfo = $login->login_verify();
56 if(!$userinfo){print qq~Content-type:text/html; Cache-Control:no-cache;\n\n<script>window.location="$url->{login}";</script>~; exit;}
57
58 my $is_admin = ($userinfo->{role} && ($userinfo->{role} eq 'company_admin' || $userinfo->{role} eq 'company_owner')) || $userinfo->{is_super_admin};
59 if(!$is_admin){print qq~Status: 403 Forbidden\nContent-Type: text/plain\n\nAdmin only.\n~; exit;}
60
61 if(($ENV{REQUEST_METHOD} || '') eq 'POST'){&api_action($userinfo);}
62 else{&api_panel($userinfo);}
63 exit;
64}
65
66&api_dispatch;
67
68
69############################################################
70# Subroutines
71############################################################
72#Tiny JSON parser (no external dep). Flat objects only - string/number/bool/null values.
73sub parse_json{
74 my($s) = @_;
75 return {} unless defined $s && length $s;
76 my %out;
77 while($s =~ /"([^"\\]*(?:\\.[^"\\]*)*)"\s*:\s*"((?:[^"\\]|\\.)*)"/g){
78 my($k, $v) = ($1, $2);
79 $v =~ s/\\"/"/g; $v =~ s/\\n/\n/g; $v =~ s/\\r/\r/g; $v =~ s/\\\\/\\/g;
80 $out{$k} = $v;
81 }
82 while($s =~ /"([^"\\]*(?:\\.[^"\\]*)*)"\s*:\s*(-?\d+(?:\.\d+)?)/g){
83 $out{$1} = $2 unless exists $out{$1};
84 }
85 while($s =~ /"([^"\\]*(?:\\.[^"\\]*)*)"\s*:\s*(true|false|null)/g){
86 my($k, $v) = ($1, $2);
87 $out{$k} = ($v eq 'true' ? 1 : $v eq 'false' ? 0 : undef) unless exists $out{$k};
88 }
89 return \%out;
90}
91
92sub api_dispatch{
93 #Rewritten to /api.cgi/v1/... by Apache. Parse PATH_INFO, dispatch to handler.
94
95 $DBH = $db->db_connect();
96 if(!$DBH){
97 $api->send_response(500, { error => 'db connect failed' });
98 exit;
99 }
100
101 if(!$api->schema_ready($db, $DBH, $database_name)){
102 $api->send_response(503, { error => 'api not provisioned' });
103 exit;
104 }
105
106 $AUTH = $api->authenticate($db, $DBH, $database_name);
107 if(!$AUTH){
108 $api->send_response(401, { error => 'invalid or missing API key', hint => 'Set Authorization: Bearer tfk_...' });
109 exit;
110 }
111
112 my $path = $ENV{PATH_INFO} || $query->url_param('path') || '';
113 my $method = uc($ENV{REQUEST_METHOD} || 'GET');
114
115 #Read JSON body for write methods
116 my $body_json = '';
117 if($method =~ /^(POST|PATCH|PUT)$/){
118 my $len = $ENV{CONTENT_LENGTH} || 0;
119 read(STDIN, $body_json, $len) if $len;
120 }
121 my $body = $body_json ? &parse_json($body_json) : {};
122
123 #Route dispatch
124 if ($path =~ m~^/v1/me/?$~) {&do_me;}
125 elsif($path =~ m~^/v1/projects/?$~) {&do_projects;}
126 elsif($path =~ m~^/v1/tasks/?$~ && $method eq 'GET') {&do_tasks_list;}
127 elsif($path =~ m~^/v1/tasks/?$~ && $method eq 'POST') {&do_tasks_create($body);}
128 elsif($path =~ m~^/v1/tasks/(\d+)/?$~ && $method eq 'GET') {&do_tasks_get($1);}
129 elsif($path =~ m~^/v1/tasks/(\d+)/?$~ && $method eq 'PATCH') {&do_tasks_update($1, $body);}
130 elsif($path =~ m~^/v1/tasks/(\d+)/comments/?$~ && $method eq 'POST'){&do_tasks_comment($1, $body);}
131 elsif($path =~ m~^/v1/users/?$~) {&do_users;}
132 elsif($path =~ m~^/v1/departments/?$~) {&do_departments;}
133 elsif($path =~ m~^/v1/task_types/?$~) {&do_task_types;}
134 else{
135 $api->send_response(404, { error => 'no such endpoint', path => $path, method => $method });
136 }
137}
138
139sub do_me{
140 $api->send_response(200, {
141 company_id => $AUTH->{company_id},
142 user_id => $AUTH->{user_id},
143 scopes => $AUTH->{scopes},
144 api_version => 'v1',
145 });
146}
147
148sub do_projects{
149 my $cid = $AUTH->{company_id};
150 my @r = $db->db_readwrite_multiple($DBH, qq~SELECT id, name, key_prefix, color FROM `${database_name}`.projects WHERE company_id='$cid' ORDER BY name LIMIT 200~, $ENV{SCRIPT_NAME}, __LINE__);
151 $api->send_response(200, { projects => \@r, count => scalar(@r) });
152}
153
154sub do_tasks_list{
155 my $cid = $AUTH->{company_id};
156 my $f = $query->Vars;
157
158 my @where = ("t.company_id='$cid'");
159 push @where, "t.status='$f->{status}'" if $f->{status} && $f->{status} =~ /^(open|in_progress|blocked|review|done|cancelled)$/;
160 push @where, "t.priority='$f->{priority}'" if $f->{priority} && $f->{priority} =~ /^(low|medium|high|urgent|critical)$/;
161 push @where, "t.project_id='$f->{project_id}'" if $f->{project_id} && $f->{project_id} =~ /^\d+$/;
162 push @where, "t.work_item_type='$f->{work_item_type}'" if $f->{work_item_type} && $f->{work_item_type} =~ /^(task|ticket|bug|epic|story|request|incident)$/;
163 push @where, "t.assigned_user_id='$f->{assigned_user_id}'" if $f->{assigned_user_id} && $f->{assigned_user_id} =~ /^\d+$/;
164
165 my $limit = $f->{limit} || 50; $limit =~ s/[^0-9]//g; $limit ||= 50; $limit = 200 if $limit > 200;
166 my $offset = $f->{offset} || 0; $offset =~ s/[^0-9]//g; $offset = 0 if $offset eq '';
167 my $w = join(' AND ', @where);
168
169 my @rows = $db->db_readwrite_multiple($DBH, qq~SELECT t.id, t.title, t.status, t.priority, t.work_item_type, t.requester_type, t.customer_email, t.customer_name, t.ticket_number, t.project_id, t.task_type_id, t.assigned_user_id, t.assigned_department_id, t.reporter_user_id, t.due_date, t.created_at, t.updated_at, t.completed_at, p.key_prefix FROM `${database_name}`.tasks t JOIN `${database_name}`.projects p ON p.id = t.project_id WHERE $w ORDER BY t.updated_at DESC LIMIT $limit OFFSET $offset~, $ENV{SCRIPT_NAME}, __LINE__);
170 foreach my $r(@rows){
171 $r->{display_id} = ($r->{key_prefix} ? "$r->{key_prefix}-$r->{ticket_number}" : "TF-$r->{id}");
172 }
173
174 $api->send_response(200, { tasks => \@rows, count => scalar(@rows), limit => $limit+0, offset => $offset+0 });
175}
176
177sub do_tasks_get{
178 my($id) = @_;
179 my $cid = $AUTH->{company_id};
180 my $r = $db->db_readwrite($DBH, qq~SELECT t.*, p.key_prefix FROM `${database_name}`.tasks t JOIN `${database_name}`.projects p ON p.id=t.project_id WHERE t.id='$id' AND t.company_id='$cid' LIMIT 1~, $ENV{SCRIPT_NAME}, __LINE__);
181 if(!$r || !$r->{id}){$api->send_response(404, { error => 'task not found' }); return;}
182 $r->{display_id} = ($r->{key_prefix} ? "$r->{key_prefix}-$r->{ticket_number}" : "TF-$r->{id}");
183 $api->send_response(200, { task => $r });
184}
185
186sub do_tasks_create{
187 my($b) = @_;
188 my $cid = $AUTH->{company_id};
189
190 my $pid = $b->{project_id};
191 $pid =~ s/[^0-9]//g if defined $pid;
192 return $api->send_response(400, { error => 'project_id required' }) unless $pid;
193
194 my $title = substr(($b->{title} || ''), 0, 280);
195 return $api->send_response(400, { error => 'title required' }) unless length $title;
196
197 my $proj = $db->db_readwrite($DBH, qq~SELECT id FROM `${database_name}`.projects WHERE id='$pid' AND company_id='$cid' LIMIT 1~, $ENV{SCRIPT_NAME}, __LINE__);
198 return $api->send_response(404, { error => 'project not found' }) unless $proj && $proj->{id};
199
200 my $col = $db->db_readwrite($DBH, qq~SELECT id FROM `${database_name}`.project_kanban_columns WHERE project_id='$pid' ORDER BY position LIMIT 1~, $ENV{SCRIPT_NAME}, __LINE__);
201 return $api->send_response(422, { error => 'project has no kanban columns' }) unless $col && $col->{id};
202
203 my $tn = $db->db_readwrite($DBH, qq~SELECT COALESCE(MAX(ticket_number),0)+1 AS n FROM `${database_name}`.tasks WHERE project_id='$pid'~, $ENV{SCRIPT_NAME}, __LINE__);
204 my $ticket_n = ($tn && $tn->{n}) ? $tn->{n} : 1;
205
206 my $prio = ($b->{priority} && $b->{priority} =~ /^(low|medium|high|urgent|critical)$/) ? $b->{priority} : 'medium';
207 my $wit = ($b->{work_item_type} && $b->{work_item_type} =~ /^(task|ticket|bug|epic|story|request|incident)$/) ? $b->{work_item_type} : 'task';
208 my $reporter = $AUTH->{user_id} || 1;
209
210 my $t_q = $title; $t_q =~ s/'/''/g;
211 my $d_q = substr(($b->{description} || ''), 0, 64000); $d_q =~ s/'/''/g;
212 my $ce = substr(($b->{customer_email} || ''), 0, 255); $ce =~ s/'/''/g;
213 my $cn = substr(($b->{customer_name} || ''), 0, 160); $cn =~ s/'/''/g;
214 my $req_type = (length $ce) ? 'external' : 'internal';
215
216 my $sql = qq~INSERT INTO `${database_name}`.tasks SET company_id='$cid', project_id='$pid', ticket_number='$ticket_n', title='$t_q', description='$d_q', column_id='$col->{id}', position_in_column=0, status='open', priority='$prio', work_item_type='$wit', requester_type='$req_type', source='api', customer_email='$ce', customer_name='$cn', reporter_user_id='$reporter'~;
217 my $r = $db->db_readwrite($DBH, $sql, $ENV{SCRIPT_NAME}, __LINE__);
218 my $new_id = ($r && $r->{mysql_insertid}) ? $r->{mysql_insertid} : 0;
219
220 #Fire outbound webhooks + SLA setup for this event (best-effort)
221 eval {
222 require MODS::PTMatrix::WebhookRouter;
223 MODS::PTMatrix::WebhookRouter->new->fire($db, $DBH, $database_name, $cid, 'task.created', {
224 task_id => $new_id, title => $title, project_id => $pid,
225 status => 'open', priority => $prio, work_item_type => $wit,
226 customer_email => $ce, customer_name => $cn, source => 'api',
227 });
228 };
229 eval {
230 require MODS::PTMatrix::SLA;
231 my $tk = $db->db_readwrite($DBH, qq~SELECT id, company_id, project_id, priority, work_item_type, assigned_department_id FROM `${database_name}`.tasks WHERE id='$new_id'~, $ENV{SCRIPT_NAME}, __LINE__);
232 MODS::PTMatrix::SLA->new->apply_for_task($db, $DBH, $database_name, $tk) if $tk;
233 };
234
235 $api->send_response(201, { task_id => $new_id, ticket_number => $ticket_n });
236}
237
238sub do_tasks_update{
239 my($id, $b) = @_;
240 my $cid = $AUTH->{company_id};
241
242 my $r = $db->db_readwrite($DBH, qq~SELECT id, status FROM `${database_name}`.tasks WHERE id='$id' AND company_id='$cid' LIMIT 1~, $ENV{SCRIPT_NAME}, __LINE__);
243 return $api->send_response(404, { error => 'task not found' }) unless $r && $r->{id};
244
245 my @sets;
246 if(defined $b->{title}){
247 my $v = substr($b->{title}, 0, 280);
248 $v =~ s/'/''/g;
249 push @sets, "title='$v'";
250 }
251 if(defined $b->{status} && $b->{status} =~ /^(open|in_progress|blocked|review|done|cancelled)$/){
252 push @sets, "status='$b->{status}'";
253 push @sets, 'completed_at=NOW()' if $b->{status} eq 'done';
254 }
255 if(defined $b->{priority} && $b->{priority} =~ /^(low|medium|high|urgent|critical)$/){
256 push @sets, "priority='$b->{priority}'";
257 }
258 if(defined $b->{assigned_user_id}){
259 my $v = $b->{assigned_user_id}; $v =~ s/[^0-9]//g;
260 push @sets, "assigned_user_id=" . (length $v ? "'$v'" : 'NULL');
261 }
262 if(defined $b->{assigned_department_id}){
263 my $v = $b->{assigned_department_id}; $v =~ s/[^0-9]//g;
264 push @sets, "assigned_department_id=" . (length $v ? "'$v', routed_at=NOW()" : 'NULL');
265 }
266
267 return $api->send_response(400, { error => 'nothing to update' }) unless @sets;
268
269 my $set = join(',', @sets);
270 $db->db_readwrite($DBH, qq~UPDATE `${database_name}`.tasks SET $set WHERE id='$id'~, $ENV{SCRIPT_NAME}, __LINE__);
271
272 #Fire webhooks + SLA transition
273 eval {
274 require MODS::PTMatrix::WebhookRouter;
275 MODS::PTMatrix::WebhookRouter->new->fire($db, $DBH, $database_name, $cid, 'task.updated', { task_id => $id, %$b });
276 };
277 if($b->{status}){
278 eval {
279 require MODS::PTMatrix::SLA;
280 if($b->{status} =~ /^(done|cancelled|resolved|closed)$/){
281 MODS::PTMatrix::SLA->new->mark_resolved($db, $DBH, $database_name, $id);
282 }
283 };
284 }
285
286 $api->send_response(200, { task_id => $id+0, updated => 1 });
287}
288
289sub do_tasks_comment{
290 my($id, $b) = @_;
291 my $cid = $AUTH->{company_id};
292
293 my $body = substr(($b->{body} || ''), 0, 64000);
294 return $api->send_response(400, { error => 'body required' }) unless length $body;
295
296 my $r = $db->db_readwrite($DBH, qq~SELECT id FROM `${database_name}`.tasks WHERE id='$id' AND company_id='$cid' LIMIT 1~, $ENV{SCRIPT_NAME}, __LINE__);
297 return $api->send_response(404, { error => 'task not found' }) unless $r && $r->{id};
298
299 my $b_q = $body; $b_q =~ s/'/''/g;
300 my $internal = $b->{internal_only} ? 1 : 0;
301 my $user_sql = $AUTH->{user_id} ? "'$AUTH->{user_id}'" : 'NULL';
302
303 my $insert = $db->db_readwrite($DBH, qq~INSERT INTO `${database_name}`.task_comments SET task_id='$id', user_id=$user_sql, body='$b_q', internal_only='$internal'~, $ENV{SCRIPT_NAME}, __LINE__);
304 my $cmt_id = ($insert && $insert->{mysql_insertid}) ? $insert->{mysql_insertid} : 0;
305
306 eval {
307 require MODS::PTMatrix::WebhookRouter;
308 MODS::PTMatrix::WebhookRouter->new->fire($db, $DBH, $database_name, $cid, 'task.commented', {
309 task_id => $id, comment_id => $cmt_id, body_excerpt => substr($body, 0, 200),
310 });
311 };
312 eval {
313 require MODS::PTMatrix::SLA;
314 MODS::PTMatrix::SLA->new->mark_response($db, $DBH, $database_name, $id) if !$internal;
315 };
316
317 $api->send_response(201, { task_id => $id+0, comment_id => $cmt_id+0 });
318}
319
320sub do_users{
321 my $cid = $AUTH->{company_id};
322 my @r = $db->db_readwrite_multiple($DBH, qq~SELECT u.id, u.display_name, u.email, u.role, u.job_title, GROUP_CONCAT(dm.department_id) AS department_ids FROM `${database_name}`.users u LEFT JOIN `${database_name}`.department_members dm ON dm.user_id = u.id WHERE u.company_id='$cid' AND u.account_status='active' GROUP BY u.id ORDER BY u.display_name LIMIT 500~, $ENV{SCRIPT_NAME}, __LINE__);
323 foreach my $row(@r){
324 if(defined $row->{department_ids} && length $row->{department_ids}){
325 $row->{department_ids} = [ map { $_ + 0 } split /,/, $row->{department_ids} ];
326 }else{
327 $row->{department_ids} = [];
328 }
329 }
330 $api->send_response(200, { users => \@r, count => scalar(@r) });
331}
332
333sub do_departments{
334 my $cid = $AUTH->{company_id};
335 my @r = $db->db_readwrite_multiple($DBH, qq~SELECT id, name, color FROM `${database_name}`.departments WHERE company_id='$cid' ORDER BY name~, $ENV{SCRIPT_NAME}, __LINE__);
336 $api->send_response(200, { departments => \@r, count => scalar(@r) });
337}
338
339sub do_task_types{
340 my $cid = $AUTH->{company_id};
341 my @r = $db->db_readwrite_multiple($DBH, qq~SELECT id, name, color FROM `${database_name}`.task_types WHERE company_id='$cid' ORDER BY name~, $ENV{SCRIPT_NAME}, __LINE__);
342 $api->send_response(200, { task_types => \@r, count => scalar(@r) });
343}
344
345#============================================================
346# api_keys entry: admin UI for API keys + webhook subscriptions
347#============================================================
348sub ak_enc{
349 my $s = shift;
350 $s = '' unless defined $s;
351 $s =~ s/'/''/g;
352 return "'$s'";
353}
354
355sub ak_int_safe{
356 my $s = shift // '';
357 $s =~ s/[^0-9]//g;
358 return length($s) ? $s : 0;
359}
360
361sub api_action{
362 my($userinfo) = @_;
363 my $cid = MODS::PTMatrix::PM::get_company_id($userinfo);
364 my $uid = MODS::PTMatrix::PM::get_user_id($userinfo);
365
366 my $dbh = $db->db_connect();
367 my $schema_ready = $api->schema_ready($db, $dbh, $database_name);
368 if(!$schema_ready){
369 $db->db_disconnect($dbh);
370 &api_panel($userinfo);
371 return;
372 }
373
374 my $act = $form->{act} || '';
375 my ($flash_kind, $flash_msg, $new_key_plain) = ('', '', '');
376
377 if($act eq 'create_key'){
378 my $name = substr($rawform{name} || 'API key', 0, 120);
379 my $scopes = $rawform{scopes} || 'read,write';
380 $scopes =~ s/[^a-z,_]//g;
381 my ($plain) = $api->issue_key($db, $dbh, $database_name,
382 company_id => $cid,
383 created_by_user_id => $uid,
384 name => $name,
385 scopes => $scopes,
386 );
387 $new_key_plain = $plain;
388 $flash_kind = 'ok';
389 $flash_msg = 'API key created. <strong>This is the ONLY time you will see the full secret</strong> &mdash; copy it now.';
390 }elsif($act eq 'delete_key'){
391 my $id = &ak_int_safe($form->{id});
392 $db->db_readwrite($dbh, qq~DELETE FROM `${database_name}`.api_keys WHERE id='$id' AND company_id='$cid'~, $ENV{SCRIPT_NAME}, __LINE__);
393 $flash_kind = 'ok'; $flash_msg = 'Key revoked.';
394 }elsif($act eq 'toggle_key'){
395 my $id = &ak_int_safe($form->{id});
396 $db->db_readwrite($dbh, qq~UPDATE `${database_name}`.api_keys SET is_active=1-is_active WHERE id='$id' AND company_id='$cid'~, $ENV{SCRIPT_NAME}, __LINE__);
397 $flash_kind = 'ok'; $flash_msg = 'Toggled.';
398 }elsif($act eq 'create_webhook'){
399 my $name = substr($rawform{name} || '', 0, 120);
400 my $target = substr($rawform{target_url} || '', 0, 1024);
401 my $events = substr($rawform{event_kinds} || '', 0, 500);
402 $events =~ s/\s//g;
403
404 if(length $name && $target =~ m~^https?://~i){
405 my $secret;
406 if(open my $fh, '<', '/dev/urandom'){
407 my $buf;
408 read($fh, $buf, 24);
409 close $fh;
410 $secret = unpack('H*', $buf);
411 }else{
412 $secret = sprintf('%016x%016x%016x', int(rand(2**32)), int(rand(2**32)), int(rand(2**32)));
413 }
414
415 my $cid_q = &ak_enc($cid);
416 my $name_q = &ak_enc($name);
417 my $target_q = &ak_enc($target);
418 my $events_q = length($events) ? &ak_enc($events) : 'NULL';
419 my $secret_q = &ak_enc($secret);
420 my $uid_q = &ak_enc($uid);
421
422 my $sql = qq~INSERT INTO `${database_name}`.webhook_subscriptions SET company_id=$cid_q, name=$name_q, target_url=$target_q, event_kinds=$events_q, signing_secret=$secret_q, created_by_user_id=$uid_q~;
423 $db->db_readwrite($dbh, $sql, $ENV{SCRIPT_NAME}, __LINE__);
424
425 $flash_kind = 'ok'; $flash_msg = 'Webhook created. Signing secret will appear in the row.';
426 }
427 }elsif($act eq 'delete_webhook'){
428 my $id = &ak_int_safe($form->{id});
429 $db->db_readwrite($dbh, qq~DELETE FROM `${database_name}`.webhook_subscriptions WHERE id='$id' AND company_id='$cid'~, $ENV{SCRIPT_NAME}, __LINE__);
430 $flash_kind = 'ok'; $flash_msg = 'Subscription removed.';
431 }elsif($act eq 'toggle_webhook'){
432 my $id = &ak_int_safe($form->{id});
433 $db->db_readwrite($dbh, qq~UPDATE `${database_name}`.webhook_subscriptions SET is_active=1-is_active WHERE id='$id' AND company_id='$cid'~, $ENV{SCRIPT_NAME}, __LINE__);
434 $flash_kind = 'ok'; $flash_msg = 'Toggled.';
435 }
436
437 $db->db_disconnect($dbh);
438 &api_panel($userinfo, $flash_kind, $flash_msg, $new_key_plain);
439}
440
441sub api_panel{
442 my($userinfo, $flash_kind, $flash_msg, $new_key_plain) = @_;
443 $flash_kind ||= '';
444 $flash_msg ||= '';
445 $new_key_plain ||= '';
446
447 my $cid = MODS::PTMatrix::PM::get_company_id($userinfo);
448 my $dbh = $db->db_connect();
449 my $schema_ready = $api->schema_ready($db, $dbh, $database_name);
450
451 my @keys = $schema_ready ? $db->db_readwrite_multiple($dbh, qq~SELECT * FROM `${database_name}`.api_keys WHERE company_id='$cid' ORDER BY is_active DESC, created_at DESC~, $ENV{SCRIPT_NAME}, __LINE__) : ();
452 my @hooks = $schema_ready ? $db->db_readwrite_multiple($dbh, qq~SELECT * FROM `${database_name}`.webhook_subscriptions WHERE company_id='$cid' ORDER BY is_active DESC, created_at DESC~, $ENV{SCRIPT_NAME}, __LINE__) : ();
453
454 $db->db_disconnect($dbh);
455
456 my @key_rows;
457 foreach my $r(@keys){
458 push @key_rows, {
459 id => $r->{id},
460 name => MODS::PTMatrix::PM::h($r->{name}),
461 secret_prefix => MODS::PTMatrix::PM::h($r->{secret_prefix}),
462 scopes => MODS::PTMatrix::PM::h($r->{scopes}),
463 is_active => $r->{is_active} ? 1 : 0,
464 active_label => $r->{is_active} ? 'Active' : 'Revoked',
465 last_used_at => $r->{last_used_at} || 'never',
466 use_count => $r->{use_count} || 0,
467 };
468 }
469
470 my @hook_rows;
471 foreach my $r(@hooks){
472 push @hook_rows, {
473 id => $r->{id},
474 name => MODS::PTMatrix::PM::h($r->{name}),
475 target_url => MODS::PTMatrix::PM::h($r->{target_url}),
476 event_kinds => MODS::PTMatrix::PM::h($r->{event_kinds} || 'all'),
477 signing_secret => MODS::PTMatrix::PM::h($r->{signing_secret}),
478 is_active => $r->{is_active} ? 1 : 0,
479 active_label => $r->{is_active} ? 'Active' : 'Paused',
480 delivered_count => $r->{delivered_count} || 0,
481 failed_count => $r->{failed_count} || 0,
482 last_delivered_at => $r->{last_delivered_at} || 'never',
483 last_error => $r->{last_error} ? MODS::PTMatrix::PM::h(substr($r->{last_error}, 0, 200)) : '',
484 has_error => $r->{last_error} ? 1 : 0,
485 };
486 }
487
488 print qq~Content-Type: text/html; charset=utf-8\nCache-Control: no-store\n\n~;
489
490 my $tvars;
491 $tvars->{keys} = \@key_rows;
492 $tvars->{has_keys} = scalar(@key_rows) ? 1 : 0;
493 $tvars->{hooks} = \@hook_rows;
494 $tvars->{has_hooks} = scalar(@hook_rows) ? 1 : 0;
495 $tvars->{new_key_plain} = MODS::PTMatrix::PM::h($new_key_plain);
496 $tvars->{has_new_key} = $new_key_plain ? 1 : 0;
497 $tvars->{schema_ready} = $schema_ready ? 1 : 0;
498 $tvars->{schema_missing} = $schema_ready ? 0 : 1;
499 $tvars->{flash_kind} = $flash_kind;
500 $tvars->{flash_msg} = $flash_msg;
501 $tvars->{has_flash} = $flash_msg ne '' ? 1 : 0;
502
503 my $body = join('', $tfile->template('tf_api_keys.html', $tvars, $userinfo));
504
505 $load->render({
506 userinfo => $userinfo,
507 page_key => 'api_keys',
508 title => 'API & Webhooks',
509 body => $body,
510 });
511}
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help