added on local at 2026-07-01 15:02:22
| 1 | #!/usr/bin/perl | |
| 2 | #====================================================================== | |
| 3 | # ContactForge -- Public REST API entry point | |
| 4 | # | |
| 5 | # Apache rewrites /api/* -> /api.cgi/* (see .htaccess). PATH_INFO is | |
| 6 | # preserved. Auth: Authorization: Bearer cf_<token> | |
| 7 | # | |
| 8 | # Endpoints (v1): | |
| 9 | # GET /me | |
| 10 | # GET /contacts (?q=&limit=&offset=) | |
| 11 | # POST /contacts | |
| 12 | # GET /contacts/:id | |
| 13 | # PATCH /contacts/:id | |
| 14 | # DELETE /contacts/:id (soft-archive) | |
| 15 | # GET /companies, POST, GET/:id, PATCH/:id | |
| 16 | # GET /deals, POST, GET/:id, PATCH/:id | |
| 17 | # GET /activities, POST | |
| 18 | # GET /tasks, POST, PATCH/:id | |
| 19 | # GET /notes, POST | |
| 20 | # | |
| 21 | # JSON in, JSON out. Errors: { "error": "...", "code": "..." } | |
| 22 | #====================================================================== | |
| 23 | use strict; use warnings; | |
| 24 | use lib '/var/www/vhosts/3dshawn.com/crm.3dshawn.com'; | |
| 25 | use CGI; | |
| 26 | $CGI::POST_MAX = 8 * 1024 * 1024; | |
| 27 | use Digest::SHA qw(sha256_hex); | |
| 28 | use MODS::DBConnect; | |
| 29 | use MODS::ContactForge::Config; | |
| 30 | ||
| 31 | my $q = CGI->new; | |
| 32 | my $db = MODS::DBConnect->new; | |
| 33 | my $cfg = MODS::ContactForge::Config->new; | |
| 34 | my $DB = $cfg->settings('database_name'); | |
| 35 | ||
| 36 | sub send_resp { | |
| 37 | my ($status, $payload) = @_; | |
| 38 | my $msg = ($status == 200) ? 'OK' : ($status == 201) ? 'Created' : | |
| 39 | ($status == 400) ? 'Bad Request' : ($status == 401) ? 'Unauthorized' : | |
| 40 | ($status == 403) ? 'Forbidden' : ($status == 404) ? 'Not Found' : | |
| 41 | ($status == 422) ? 'Unprocessable Entity' : 'Internal Server Error'; | |
| 42 | print "Status: $status $msg\nContent-Type: application/json; charset=utf-8\nX-CF-API-Version: v1\n\n"; | |
| 43 | print to_json($payload); | |
| 44 | exit; | |
| 45 | } | |
| 46 | ||
| 47 | sub to_json { | |
| 48 | my ($v) = @_; | |
| 49 | return 'null' unless defined $v; | |
| 50 | if (ref $v eq 'ARRAY') { return '[' . join(',', map { to_json($_) } @$v) . ']'; } | |
| 51 | if (ref $v eq 'HASH') { | |
| 52 | my @p; foreach my $k (sort keys %$v) { push @p, '"' . esc($k) . '":' . to_json($v->{$k}); } | |
| 53 | return '{' . join(',', @p) . '}'; | |
| 54 | } | |
| 55 | return "$v" if $v =~ /^-?\d+(\.\d+)?$/; | |
| 56 | return '"' . esc($v) . '"'; | |
| 57 | } | |
| 58 | sub esc { | |
| 59 | my ($s) = @_; $s = '' unless defined $s; | |
| 60 | $s =~ s/\\/\\\\/g; $s =~ s/"/\\"/g; $s =~ s/\n/\\n/g; $s =~ s/\r/\\r/g; $s =~ s/\t/\\t/g; | |
| 61 | $s =~ s/[\x00-\x1f]//g; | |
| 62 | return $s; | |
| 63 | } | |
| 64 | ||
| 65 | sub parse_json { | |
| 66 | my ($s) = @_; | |
| 67 | return {} unless defined $s && length $s; | |
| 68 | my %out; | |
| 69 | while ($s =~ /"([^"\\]*(?:\\.[^"\\]*)*)"\s*:\s*"((?:[^"\\]|\\.)*)"/g) { | |
| 70 | my ($k, $v) = ($1, $2); $v =~ s/\\"/"/g; $v =~ s/\\n/\n/g; $v =~ s/\\r/\r/g; $v =~ s/\\\\/\\/g; | |
| 71 | $out{$k} = $v; | |
| 72 | } | |
| 73 | while ($s =~ /"([^"\\]*(?:\\.[^"\\]*)*)"\s*:\s*(-?\d+(?:\.\d+)?)/g) { $out{$1} = $2 unless exists $out{$1}; } | |
| 74 | while ($s =~ /"([^"\\]*(?:\\.[^"\\]*)*)"\s*:\s*(true|false|null)/g) { | |
| 75 | my ($k, $v) = ($1, $2); | |
| 76 | $out{$k} = ($v eq 'true' ? 1 : $v eq 'false' ? 0 : undef) unless exists $out{$k}; | |
| 77 | } | |
| 78 | return \%out; | |
| 79 | } | |
| 80 | ||
| 81 | my $dbh = $db->db_connect(); | |
| 82 | send_resp(500, { error => 'db connect failed' }) unless $dbh; | |
| 83 | ||
| 84 | # Auth ----------------------------------------------------------------- | |
| 85 | my $hdr = $ENV{HTTP_AUTHORIZATION} || ''; | |
| 86 | my $tok; if ($hdr =~ /^Bearer\s+(\S+)/i) { $tok = $1; } | |
| 87 | send_resp(401, { error => 'invalid or missing token', hint => 'Set Authorization: Bearer cf_...' }) unless $tok; | |
| 88 | ||
| 89 | my $hash = sha256_hex($tok); | |
| 90 | my $r = $db->db_readwrite($dbh, qq~ | |
| 91 | SELECT k.id AS key_id, k.user_id, u.team_id | |
| 92 | FROM `${DB}`.api_keys k | |
| 93 | LEFT JOIN `${DB}`.users u ON u.id = k.user_id | |
| 94 | WHERE k.token_hash='$hash' AND k.revoked_at IS NULL | |
| 95 | LIMIT 1 | |
| 96 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 97 | send_resp(401, { error => 'invalid token' }) unless $r && $r->{key_id}; | |
| 98 | my $uid = $r->{user_id}; | |
| 99 | my $tid = $r->{team_id} || 0; | |
| 100 | ||
| 101 | # Touch last_used (best-effort) | |
| 102 | my $ip = $ENV{REMOTE_ADDR} || ''; $ip =~ s/[^0-9a-f:.]//gi; | |
| 103 | eval { $db->db_readwrite($dbh, qq~UPDATE `${DB}`.api_keys SET last_used_at=NOW() WHERE id='$r->{key_id}'~, $ENV{SCRIPT_NAME}, __LINE__); }; | |
| 104 | ||
| 105 | # Path + body ---------------------------------------------------------- | |
| 106 | my $path = $ENV{PATH_INFO} || $q->url_param('path') || ''; | |
| 107 | $path =~ s|^/api||; # tolerate /api prefix | |
| 108 | my $method = uc($ENV{REQUEST_METHOD} || 'GET'); | |
| 109 | my $body = {}; | |
| 110 | if ($method =~ /^(POST|PATCH|PUT)$/) { | |
| 111 | my $raw = ''; my $len = $ENV{CONTENT_LENGTH} || 0; | |
| 112 | if ($len) { read(STDIN, $raw, $len); } | |
| 113 | $body = parse_json($raw); | |
| 114 | } | |
| 115 | ||
| 116 | # Dispatch ------------------------------------------------------------- | |
| 117 | if ($path =~ m~^/me/?$~) { do_me(); } | |
| 118 | elsif ($path =~ m~^/contacts/?$~ && $method eq 'GET') { list_contacts(); } | |
| 119 | elsif ($path =~ m~^/contacts/?$~ && $method eq 'POST') { create_contact($body); } | |
| 120 | elsif ($path =~ m~^/contacts/(\d+)/?$~ && $method eq 'GET') { get_contact($1); } | |
| 121 | elsif ($path =~ m~^/contacts/(\d+)/?$~ && $method eq 'PATCH') { update_contact($1, $body); } | |
| 122 | elsif ($path =~ m~^/contacts/(\d+)/?$~ && $method eq 'DELETE') { archive_contact($1); } | |
| 123 | elsif ($path =~ m~^/companies/?$~ && $method eq 'GET') { list_simple('companies', 'name'); } | |
| 124 | elsif ($path =~ m~^/companies/?$~ && $method eq 'POST') { create_company($body); } | |
| 125 | elsif ($path =~ m~^/companies/(\d+)/?$~ && $method eq 'GET') { get_one('companies', $1); } | |
| 126 | elsif ($path =~ m~^/deals/?$~ && $method eq 'GET') { list_deals(); } | |
| 127 | elsif ($path =~ m~^/deals/?$~ && $method eq 'POST') { create_deal($body); } | |
| 128 | elsif ($path =~ m~^/deals/(\d+)/?$~ && $method eq 'GET') { get_one('deals', $1); } | |
| 129 | elsif ($path =~ m~^/deals/(\d+)/?$~ && $method eq 'PATCH') { update_deal($1, $body); } | |
| 130 | elsif ($path =~ m~^/tasks/?$~ && $method eq 'GET') { list_simple('tasks', 'due_date'); } | |
| 131 | elsif ($path =~ m~^/tasks/?$~ && $method eq 'POST') { create_task($body); } | |
| 132 | elsif ($path =~ m~^/notes/?$~ && $method eq 'POST') { create_note($body); } | |
| 133 | elsif ($path =~ m~^/activities/?$~ && $method eq 'POST') { create_activity($body); } | |
| 134 | elsif ($path =~ m~^/pipelines/?$~ && $method eq 'GET') { list_simple('pipelines', 'name'); } | |
| 135 | else { | |
| 136 | send_resp(404, { error => 'no such endpoint', path => $path, method => $method }); | |
| 137 | } | |
| 138 | ||
| 139 | # ------------------------------------------------------------------ # | |
| 140 | sub do_me { | |
| 141 | send_resp(200, { user_id => $uid+0, team_id => $tid+0, api_version => 'v1' }); | |
| 142 | } | |
| 143 | ||
| 144 | sub _team_clause { return "team_id='$tid'"; } | |
| 145 | ||
| 146 | sub list_contacts { | |
| 147 | my $f = $q->Vars; | |
| 148 | my $limit = $f->{limit} || 50; $limit =~ s/[^0-9]//g; $limit ||= 50; $limit = 200 if $limit > 200; | |
| 149 | my $offset = $f->{offset} || 0; $offset =~ s/[^0-9]//g; | |
| 150 | my @where = (_team_clause(), 'archived_at IS NULL'); | |
| 151 | if ($f->{q}) { | |
| 152 | my $qq = $f->{q}; $qq =~ s/'/''/g; $qq =~ s/%/\\%/g; | |
| 153 | push @where, "(first_name LIKE '%$qq%' OR last_name LIKE '%$qq%' OR email LIKE '%$qq%')"; | |
| 154 | } | |
| 155 | my $where = join(' AND ', @where); | |
| 156 | my @rows = $db->db_readwrite_multiple($dbh, qq~ | |
| 157 | SELECT id, first_name, last_name, email, phone, title, company_id, created_at, updated_at | |
| 158 | FROM `${DB}`.contacts WHERE $where ORDER BY id DESC LIMIT $limit OFFSET $offset | |
| 159 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 160 | send_resp(200, { contacts => \@rows, count => scalar(@rows), limit => $limit+0, offset => $offset+0 }); | |
| 161 | } | |
| 162 | ||
| 163 | sub create_contact { | |
| 164 | my ($b) = @_; | |
| 165 | my $fn = substr($b->{first_name} || '', 0, 120); | |
| 166 | my $ln = substr($b->{last_name} || '', 0, 120); | |
| 167 | my $em = lc(substr($b->{email} || '', 0, 255)); | |
| 168 | send_resp(400, { error => 'first_name OR email required' }) unless length($fn) || length($em); | |
| 169 | my %q; | |
| 170 | foreach my $k (qw(first_name last_name email phone title)) { | |
| 171 | my $v = substr($b->{$k} || '', 0, 255); $v =~ s/'/''/g; $q{$k} = $v; | |
| 172 | } | |
| 173 | my $co = $b->{company_id}; $co =~ s/[^0-9]//g if defined $co; | |
| 174 | my $co_sql = (defined $co && length $co) ? "'$co'" : 'NULL'; | |
| 175 | my $r = $db->db_readwrite($dbh, qq~ | |
| 176 | INSERT INTO `${DB}`.contacts | |
| 177 | SET team_id='$tid', | |
| 178 | first_name='$q{first_name}', last_name='$q{last_name}', | |
| 179 | email='$q{email}', phone='$q{phone}', title='$q{title}', | |
| 180 | company_id=$co_sql, owner_user_id='$uid', source='api' | |
| 181 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 182 | my $new_id = ($r && $r->{mysql_insertid}) ? $r->{mysql_insertid} : 0; | |
| 183 | send_resp(201, { contact_id => $new_id+0 }); | |
| 184 | } | |
| 185 | ||
| 186 | sub get_contact { | |
| 187 | my ($id) = @_; | |
| 188 | my $row = $db->db_readwrite($dbh, qq~ | |
| 189 | SELECT * FROM `${DB}`.contacts WHERE id='$id' AND ~ . _team_clause() . qq~ LIMIT 1 | |
| 190 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 191 | send_resp(404, { error => 'contact not found' }) unless $row && $row->{id}; | |
| 192 | send_resp(200, { contact => $row }); | |
| 193 | } | |
| 194 | ||
| 195 | sub update_contact { | |
| 196 | my ($id, $b) = @_; | |
| 197 | my @sets; | |
| 198 | foreach my $k (qw(first_name last_name email phone title)) { | |
| 199 | next unless defined $b->{$k}; | |
| 200 | my $v = substr($b->{$k}, 0, 255); $v =~ s/'/''/g; | |
| 201 | push @sets, "$k='$v'"; | |
| 202 | } | |
| 203 | if (defined $b->{company_id}) { | |
| 204 | my $v = $b->{company_id}; $v =~ s/[^0-9]//g; | |
| 205 | push @sets, "company_id=" . (length $v ? "'$v'" : 'NULL'); | |
| 206 | } | |
| 207 | send_resp(400, { error => 'nothing to update' }) unless @sets; | |
| 208 | my $set = join(',', @sets); | |
| 209 | my $ok = $db->db_readwrite($dbh, qq~ | |
| 210 | UPDATE `${DB}`.contacts SET $set WHERE id='$id' AND ~ . _team_clause() . qq~ | |
| 211 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 212 | send_resp(200, { contact_id => $id+0, updated => 1 }); | |
| 213 | } | |
| 214 | ||
| 215 | sub archive_contact { | |
| 216 | my ($id) = @_; | |
| 217 | $db->db_readwrite($dbh, qq~ | |
| 218 | UPDATE `${DB}`.contacts SET archived_at=NOW() WHERE id='$id' AND ~ . _team_clause() . qq~ | |
| 219 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 220 | send_resp(200, { contact_id => $id+0, archived => 1 }); | |
| 221 | } | |
| 222 | ||
| 223 | sub create_company { | |
| 224 | my ($b) = @_; | |
| 225 | my $name = substr($b->{name} || '', 0, 255); | |
| 226 | send_resp(400, { error => 'name required' }) unless length $name; | |
| 227 | my %q; | |
| 228 | foreach my $k (qw(name domain industry size_label phone)) { | |
| 229 | my $v = substr($b->{$k} || '', 0, 255); $v =~ s/'/''/g; $q{$k} = $v; | |
| 230 | } | |
| 231 | my $r = $db->db_readwrite($dbh, qq~ | |
| 232 | INSERT INTO `${DB}`.companies | |
| 233 | SET team_id='$tid', name='$q{name}', domain='$q{domain}', | |
| 234 | industry='$q{industry}', size_label='$q{size_label}', | |
| 235 | phone='$q{phone}', owner_user_id='$uid' | |
| 236 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 237 | my $new_id = ($r && $r->{mysql_insertid}) ? $r->{mysql_insertid} : 0; | |
| 238 | send_resp(201, { company_id => $new_id+0 }); | |
| 239 | } | |
| 240 | ||
| 241 | sub get_one { | |
| 242 | my ($table, $id) = @_; | |
| 243 | my $row = $db->db_readwrite($dbh, qq~ | |
| 244 | SELECT * FROM `${DB}`.$table WHERE id='$id' AND ~ . _team_clause() . qq~ LIMIT 1 | |
| 245 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 246 | send_resp(404, { error => 'not found' }) unless $row && $row->{id}; | |
| 247 | send_resp(200, { $table => $row }); | |
| 248 | } | |
| 249 | ||
| 250 | sub list_simple { | |
| 251 | my ($table, $order_col) = @_; | |
| 252 | my $f = $q->Vars; | |
| 253 | my $limit = $f->{limit} || 50; $limit =~ s/[^0-9]//g; $limit = 200 if $limit > 200; $limit ||= 50; | |
| 254 | my $offset = $f->{offset} || 0; $offset =~ s/[^0-9]//g; | |
| 255 | my @rows = $db->db_readwrite_multiple($dbh, qq~ | |
| 256 | SELECT * FROM `${DB}`.$table WHERE ~ . _team_clause() . qq~ | |
| 257 | ORDER BY $order_col DESC LIMIT $limit OFFSET $offset | |
| 258 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 259 | send_resp(200, { $table => \@rows, count => scalar(@rows) }); | |
| 260 | } | |
| 261 | ||
| 262 | sub list_deals { | |
| 263 | my $f = $q->Vars; | |
| 264 | my $limit = $f->{limit} || 50; $limit =~ s/[^0-9]//g; $limit = 200 if $limit > 200; $limit ||= 50; | |
| 265 | my @where = (_team_clause()); | |
| 266 | if ($f->{stage_id} && $f->{stage_id} =~ /^\d+$/) { | |
| 267 | push @where, "deal_stage_id='$f->{stage_id}'"; | |
| 268 | } | |
| 269 | if ($f->{is_won} && $f->{is_won} eq '1') { push @where, "is_won=1"; } | |
| 270 | my $where = join(' AND ', @where); | |
| 271 | my @rows = $db->db_readwrite_multiple($dbh, qq~ | |
| 272 | SELECT * FROM `${DB}`.deals WHERE $where ORDER BY id DESC LIMIT $limit | |
| 273 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 274 | send_resp(200, { deals => \@rows, count => scalar(@rows) }); | |
| 275 | } | |
| 276 | ||
| 277 | sub create_deal { | |
| 278 | my ($b) = @_; | |
| 279 | my $name = substr($b->{name} || '', 0, 255); | |
| 280 | send_resp(400, { error => 'name required' }) unless length $name; | |
| 281 | my $amount = $b->{amount_cents} || 0; $amount =~ s/[^0-9]//g; $amount ||= 0; | |
| 282 | my $stage = $b->{deal_stage_id}; $stage =~ s/[^0-9]//g if defined $stage; | |
| 283 | my $contact = $b->{contact_id}; $contact =~ s/[^0-9]//g if defined $contact; | |
| 284 | my $company = $b->{company_id}; $company =~ s/[^0-9]//g if defined $company; | |
| 285 | my $n_q = $name; $n_q =~ s/'/''/g; | |
| 286 | my $stage_sql = (length($stage||'') ? "'$stage'" : 'NULL'); | |
| 287 | my $contact_sql = (length($contact||'') ? "'$contact'" : 'NULL'); | |
| 288 | my $company_sql = (length($company||'') ? "'$company'" : 'NULL'); | |
| 289 | my $r = $db->db_readwrite($dbh, qq~ | |
| 290 | INSERT INTO `${DB}`.deals | |
| 291 | SET team_id='$tid', name='$n_q', | |
| 292 | amount_cents='$amount', currency='USD', | |
| 293 | deal_stage_id=$stage_sql, contact_id=$contact_sql, company_id=$company_sql, | |
| 294 | owner_user_id='$uid' | |
| 295 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 296 | my $new_id = ($r && $r->{mysql_insertid}) ? $r->{mysql_insertid} : 0; | |
| 297 | send_resp(201, { deal_id => $new_id+0 }); | |
| 298 | } | |
| 299 | ||
| 300 | sub update_deal { | |
| 301 | my ($id, $b) = @_; | |
| 302 | my @sets; | |
| 303 | if (defined $b->{name}) { my $v = substr($b->{name},0,255); $v =~ s/'/''/g; push @sets, "name='$v'"; } | |
| 304 | if (defined $b->{amount_cents}){ my $v = $b->{amount_cents}; $v =~ s/[^0-9]//g; push @sets, "amount_cents='$v'"; } | |
| 305 | if (defined $b->{deal_stage_id}){my $v = $b->{deal_stage_id}; $v =~ s/[^0-9]//g; push @sets, "deal_stage_id=" . (length $v ? "'$v'" : 'NULL'); } | |
| 306 | if (defined $b->{is_won}) { my $v = $b->{is_won} ? 1 : 0; push @sets, "is_won='$v'"; } | |
| 307 | send_resp(400, { error => 'nothing to update' }) unless @sets; | |
| 308 | my $set = join(',', @sets); | |
| 309 | $db->db_readwrite($dbh, qq~UPDATE `${DB}`.deals SET $set WHERE id='$id' AND ~ . _team_clause() . qq~~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 310 | send_resp(200, { deal_id => $id+0, updated => 1 }); | |
| 311 | } | |
| 312 | ||
| 313 | sub create_task { | |
| 314 | my ($b) = @_; | |
| 315 | my $title = substr($b->{title} || '', 0, 280); | |
| 316 | send_resp(400, { error => 'title required' }) unless length $title; | |
| 317 | my $t_q = $title; $t_q =~ s/'/''/g; | |
| 318 | my $due = $b->{due_date} || ''; $due =~ s/[^0-9\-: ]//g; | |
| 319 | my $due_sql = length($due) ? "'$due'" : 'NULL'; | |
| 320 | my $contact = $b->{contact_id}; $contact =~ s/[^0-9]//g if defined $contact; | |
| 321 | my $deal = $b->{deal_id}; $deal =~ s/[^0-9]//g if defined $deal; | |
| 322 | my $r = $db->db_readwrite($dbh, qq~ | |
| 323 | INSERT INTO `${DB}`.tasks | |
| 324 | SET team_id='$tid', title='$t_q', due_date=$due_sql, | |
| 325 | contact_id=~ . (length($contact||'') ? "'$contact'" : 'NULL') . qq~, | |
| 326 | deal_id=~ . (length($deal||'') ? "'$deal'" : 'NULL') . qq~, | |
| 327 | owner_user_id='$uid', status='open' | |
| 328 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 329 | my $new_id = ($r && $r->{mysql_insertid}) ? $r->{mysql_insertid} : 0; | |
| 330 | send_resp(201, { task_id => $new_id+0 }); | |
| 331 | } | |
| 332 | ||
| 333 | sub create_note { | |
| 334 | my ($b) = @_; | |
| 335 | my $body_text = substr($b->{body} || '', 0, 64000); | |
| 336 | send_resp(400, { error => 'body required' }) unless length $body_text; | |
| 337 | my $bq = $body_text; $bq =~ s/'/''/g; | |
| 338 | my $contact = $b->{contact_id}; $contact =~ s/[^0-9]//g if defined $contact; | |
| 339 | my $deal = $b->{deal_id}; $deal =~ s/[^0-9]//g if defined $deal; | |
| 340 | my $r = $db->db_readwrite($dbh, qq~ | |
| 341 | INSERT INTO `${DB}`.notes | |
| 342 | SET team_id='$tid', body='$bq', user_id='$uid', | |
| 343 | contact_id=~ . (length($contact||'') ? "'$contact'" : 'NULL') . qq~, | |
| 344 | deal_id=~ . (length($deal||'') ? "'$deal'" : 'NULL') . qq~ | |
| 345 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 346 | my $new_id = ($r && $r->{mysql_insertid}) ? $r->{mysql_insertid} : 0; | |
| 347 | send_resp(201, { note_id => $new_id+0 }); | |
| 348 | } | |
| 349 | ||
| 350 | sub create_activity { | |
| 351 | my ($b) = @_; | |
| 352 | my $kind = lc($b->{kind} || 'note'); | |
| 353 | $kind = 'note' unless $kind =~ /^(call|email|meeting|note|other)$/; | |
| 354 | my $subj = substr($b->{subject} || '', 0, 280); $subj =~ s/'/''/g; | |
| 355 | my $body_text = substr($b->{body} || '', 0, 32000); $body_text =~ s/'/''/g; | |
| 356 | my $contact = $b->{contact_id}; $contact =~ s/[^0-9]//g if defined $contact; | |
| 357 | my $deal = $b->{deal_id}; $deal =~ s/[^0-9]//g if defined $deal; | |
| 358 | my $r = $db->db_readwrite($dbh, qq~ | |
| 359 | INSERT INTO `${DB}`.activities | |
| 360 | SET team_id='$tid', kind='$kind', subject='$subj', body='$body_text', | |
| 361 | user_id='$uid', | |
| 362 | contact_id=~ . (length($contact||'') ? "'$contact'" : 'NULL') . qq~, | |
| 363 | deal_id=~ . (length($deal||'') ? "'$deal'" : 'NULL') . qq~ | |
| 364 | ~, $ENV{SCRIPT_NAME}, __LINE__); | |
| 365 | my $new_id = ($r && $r->{mysql_insertid}) ? $r->{mysql_insertid} : 0; | |
| 366 | send_resp(201, { activity_id => $new_id+0 }); | |
| 367 | } |