[livejournal] r21925: LJSUP-12148: JSON-RPC
Committer: sbelyaev
LJSUP-12148: JSON-RPCU trunk/bin/upgrading/proplists.dat U trunk/bin/upgrading/update-db-general.pl A trunk/cgi-bin/LJ/API/ A trunk/cgi-bin/LJ/API/Error.pm A trunk/cgi-bin/LJ/API/Namespaces.pm A trunk/cgi-bin/LJ/API/Repost.pm A trunk/cgi-bin/LJ/Controller/ A trunk/cgi-bin/LJ/Controller/API/ A trunk/cgi-bin/LJ/Controller/API/JSON.pm A trunk/cgi-bin/LJ/Entry/ A trunk/cgi-bin/LJ/Entry/Repost.pm U trunk/cgi-bin/LJ/Entry.pm A trunk/cgi-bin/LJ/JSON/ A trunk/cgi-bin/LJ/JSON/RPC/ A trunk/cgi-bin/LJ/JSON/RPC/Item.pm A trunk/cgi-bin/LJ/JSON/RPC.pm A trunk/cgi-bin/LJ/Router/ A trunk/cgi-bin/LJ/Router/API.pm U trunk/cgi-bin/ljlib.pl
Modified: trunk/bin/upgrading/proplists.dat
===================================================================
--- trunk/bin/upgrading/proplists.dat 2012-05-12 09:03:36 UTC (rev 21924)
+++ trunk/bin/upgrading/proplists.dat 2012-05-12 09:05:55 UTC (rev 21925)
@@ -1836,3 +1836,9 @@
prettyname: Crosspost to vkontakte
sortorder: 114
+logproplist.repost_link:
+ datatype: char
+ des: repost link information
+ prettyname: Repost link informaton
+ sortorder: 114
+
Modified: trunk/bin/upgrading/update-db-general.pl
===================================================================
--- trunk/bin/upgrading/update-db-general.pl 2012-05-12 09:03:36 UTC (rev 21924)
+++ trunk/bin/upgrading/update-db-general.pl 2012-05-12 09:05:55 UTC (rev 21925)
@@ -3399,6 +3399,17 @@
)
EOC
+register_tablecreate('repost2', <<'EOC');
+CREATE TABLE `repost2` (
+ `journalid` int(10) NOT NULL,
+ `jitemid` int(11) NOT NULL,
+ `reposterid` int(10) NOT NULL,
+ `reposted_jitemid` int(11) NOT NULL,
+ PRIMARY KEY ( `journalid`, `reposterid`, `jitemid` ),
+ KEY `jitemid` ( `journalid`, `jitemid` )
+)
+EOC
+
post_create("clients",
"sqltry" => "INSERT INTO clients (client) SELECT DISTINCT client FROM logins",
);
Added: trunk/cgi-bin/LJ/API/Error.pm
===================================================================
--- trunk/cgi-bin/LJ/API/Error.pm (rev 0)
+++ trunk/cgi-bin/LJ/API/Error.pm 2012-05-12 09:05:55 UTC (rev 21925)
@@ -0,0 +1,41 @@
+package LJ::API::Error;
+
+use strict;
+use warnings;
+
+use LJ::Lang;
+
+my %errors = (
+ 'unknown_error' => { 'error_code' => -10000,
+ 'error_var' => 'api.error.unknwon_error', },
+
+ 'invalid_params' => { 'error_code' => -32602,
+ 'error_text' => 'Invalid params', },
+
+ 'invalid_data_format' => { 'error_code' => -10001,
+ 'error_var' => 'api.error.invalid_data_format', },
+
+ 'invalid_method' => { 'error_code' => -32601,
+ 'error_text' => 'Method not found', },
+
+ 'entry_not_found' => { 'error_code' => -10002,
+ 'error_var' => 'api.error.entry_not_found', },
+
+);
+
+# reserved: 10001 code for custom error
+sub get_error {
+ my ($class, $error_type, $options) = @_;
+ die "unknwon error" unless $error_type;
+
+ my $error = $errors{$error_type};
+ die "unknwon error" unless $error;
+
+ my $lang_var = $error->{'error_var'};
+ my $text = $lang_var ? LJ::Lang::ml($lang_var, $options) : $error->{'error_text'};
+
+ return { 'error' => { 'error_code' => $error->{'error_code'},
+ 'error_message' => $text, },
+ };
+}
+
Added: trunk/cgi-bin/LJ/API/Namespaces.pm
===================================================================
--- trunk/cgi-bin/LJ/API/Namespaces.pm (rev 0)
+++ trunk/cgi-bin/LJ/API/Namespaces.pm 2012-05-12 09:05:55 UTC (rev 21925)
@@ -0,0 +1,59 @@
+package LJ::API::Namespaces;
+
+use strict;
+use warnings;
+use LJ::API::Error;
+use LJ::API::Repost;
+use attributes;
+
+my %namespace_map = (
+ 'repost' => 'LJ::API::Repost',
+);
+
+sub __is_public {
+ my ($class, $function) = @_;
+
+ my @attrs = attributes::get($function);
+
+ foreach my $attr (@attrs) {
+ if ($attr eq 'method') {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+sub call {
+ my ($class, $method, $data) = @_;
+
+ my ($namespace_name, $function) = split(/\./, $method);
+ my $namespace = $namespace_map{$namespace_name || ''};
+
+ if (!$namespace || !$function) {
+ warn "error invalid_method";
+ return LJ::API::Error->get_error('invalid_method');
+ }
+
+ my $handler = $namespace->can($function);
+
+ if (!$handler || !$class->__is_public($handler)) {
+ return LJ::API::Error->get_error('invalid_method');
+ }
+
+ my $result = {};
+
+ eval {
+ $result = $handler->($namespace, $data);
+ };
+
+ if ($@) {
+ warn $@;
+ $result = LJ::API::Error->get_error('unknwon_error');
+ }
+
+ return $result;
+
+}
+
+
Added: trunk/cgi-bin/LJ/API/Repost.pm
===================================================================
--- trunk/cgi-bin/LJ/API/Repost.pm (rev 0)
+++ trunk/cgi-bin/LJ/API/Repost.pm 2012-05-12 09:05:55 UTC (rev 21925)
@@ -0,0 +1,84 @@
+package LJ::API::Repost;
+
+use strict;
+use warnings;
+
+use LJ::Entry;
+use LJ::API::Error;
+use LJ::Entry::Repost;
+
+sub create : method {
+ my ($self, $options) = @_;
+
+ return LJ::API::Error->get_error('invalid_params') unless
+ $options->{'url'};
+
+ my $u = LJ::get_remote();
+ if ($options->{'target'}) {
+ $u = LJ::load_user($options->{'target'});
+ }
+
+ return LJ::API::Error->get_error('invalid_params') unless $u;
+ return LJ::API::Error->get_error('invalid_params') unless $options->{'timezone'};
+
+ my $url = $options->{'url'};
+ my $entry = LJ::Entry->new_from_url($url);
+
+ return LJ::API::Error->get_error('invalid_params') unless $entry;
+
+ my $timezone = $options->{'timezone'};
+
+ my $result = LJ::Entry::Repost->create( $u, # destination journal
+ $entry, # entry to be reposted
+ $timezone ); # timezone for repost
+
+ return $result;
+}
+
+sub delete : method {
+ my ($self, $options) = @_;
+
+ return LJ::API::Error->get_error('invalid_params') unless
+ $options->{'url'};
+
+ my $u = LJ::get_remote();
+ if ($options->{'target'}) {
+ $u = LJ::load_user($options->{'target'});
+ }
+
+ return LJ::API::Error->get_error('invalid_params') unless $u;
+
+ my $url = $options->{'url'};
+ my $entry = LJ::Entry->new_from_url($url);
+
+ return LJ::API::Error->get_error('invalid_params') unless $entry;
+
+ my $result = LJ::Entry::Repost->delete( $u, # destination journal
+ $entry,); # entry to be reposted
+ return $result;
+}
+
+sub get_status : method {
+ my ($self, $options) = @_;
+
+ return LJ::API::Error->get_error('invalid_params') unless
+ $options->{'url'};
+
+ my $u = LJ::get_remote();
+ if ($options->{'target'}) {
+ $u = LJ::load_user($options->{'target'});
+ }
+
+ return LJ::API::Error->get_error('invalid_params') unless $u;
+
+ my $url = $options->{'url'};
+ my $entry = LJ::Entry->new_from_url($url);
+
+ return LJ::API::Error->get_error('invalid_params') unless $entry;
+
+ my $result = LJ::Entry::Repost->get_status( $u, # destination journal
+ $entry,); # entry to be reposted
+ return $result;
+}
+
+1;
Added: trunk/cgi-bin/LJ/Controller/API/JSON.pm
===================================================================
--- trunk/cgi-bin/LJ/Controller/API/JSON.pm (rev 0)
+++ trunk/cgi-bin/LJ/Controller/API/JSON.pm 2012-05-12 09:05:55 UTC (rev 21925)
@@ -0,0 +1,34 @@
+package LJ::Controller::API::JSON;
+use strict;
+use warnings;
+
+use LJ::Lang;
+use LJ::Response::CachedTemplate;
+use LJ::Response::Error;
+use LJ::Response::Redirect;
+use LJ::Request;
+use Data::Dumper;
+
+use base qw{ LJ::Controller };
+use LJ::API::Error;
+use LJ::API::Namespaces;
+use LJ::JSON::RPC;
+
+sub process {
+ my ($self) = @_;
+
+ my $remote = LJ::get_remote();
+ my $raw_content = LJ::Request->raw_content;
+
+ my $rpc = LJ::JSON::RPC->new(LJ::Request->uri, $raw_content);
+ $rpc->call( sub {
+ return LJ::API::Namespaces->call(@_);
+ });
+
+ return $rpc->response;
+} # process
+
+sub need_res {
+}
+
+1;
Added: trunk/cgi-bin/LJ/Entry/Repost.pm
===================================================================
--- trunk/cgi-bin/LJ/Entry/Repost.pm (rev 0)
+++ trunk/cgi-bin/LJ/Entry/Repost.pm 2012-05-12 09:05:55 UTC (rev 21925)
@@ -0,0 +1,290 @@
+package LJ::Entry::Repost;
+
+use strict;
+use warnings;
+
+require 'ljlib.pl';
+require 'ljprotocol.pl';
+use LJ::Lang;
+
+sub __get_count {
+ my ($u, $jitemid) = @_;
+ my $dbcr = LJ::get_cluster_master($u)
+ or die "get cluster for journal failed";
+
+ my ($count_jitemid) = $dbcr->selectrow_array( 'SELECT COUNT(reposted_jitemid) ' .
+ 'FROM repost2 ' .
+ 'WHERE journalid = ? AND jitemid = ?',
+ undef,
+ $u->userid,
+ $jitemid, );
+ return $count_jitemid;
+
+}
+
+sub __get_repostid {
+ my ($u, $jitemid, $reposterid) = @_;
+ my $dbcr = LJ::get_cluster_master($u)
+ or die "get cluster for journal failed";
+
+ my ($repost_jitemid) = $dbcr->selectrow_array( 'SELECT reposted_jitemid ' .
+ 'FROM repost2 ' .
+ 'WHERE journalid = ? AND jitemid = ? AND reposterid = ?',
+ undef,
+ $u->userid,
+ $jitemid,
+ $reposterid, );
+ return $repost_jitemid;
+}
+
+sub __create_repost_record {
+ my ($u, $itemid, $repost_journalid, $repost_itemid) = @_;
+
+ $u->do('INSERT INTO repost2 VALUES(?,?,?,?)',
+ undef,
+ $u->userid,
+ $itemid,
+ $repost_journalid,
+ $repost_itemid, );
+}
+
+sub __delete_repost_record {
+ my ($u, $itemid, $reposterid) = @_;
+
+ $u->do('DELETE FROM repost2 WHERE journalid = ? AND jitemid = ? AND reposterid = ?',
+ undef,
+ $u->userid,
+ $itemid,
+ $reposterid,);
+}
+
+sub __create_post {
+ my ($u, $timezone, $url, $error) = @_;
+
+ my $err = 0;
+ my $flags = { 'noauth' => 1,
+ 'use_custom_time' => 0,
+ 'allow_dupsing_post' => 1,
+ 'u' => $u };
+
+ my %req = ( 'username' => $u->user,
+ 'event' => LJ::Lang::ml('repost.text', { 'url' => $url}),
+ 'subject' => '',
+ 'tz' => $timezone,
+ );
+
+ # move to LJ::API
+ my $res = LJ::Protocol::do_request("postevent", \%req, \$err, $flags);
+
+ my $fail = !defined $res->{itemid} && $res->{message};
+ if ($fail) {
+ $$error = $res->{message};
+ return;
+ }
+
+ return LJ::Entry->new($u, jitemid => $res->{'itemid'} );
+}
+
+sub __create_repost {
+ my ($opts) = @_;
+
+ my $u = $opts->{'u'};
+ my $entry_obj = $opts->{'entry_obj'};
+ my $timezone = $opts->{'timezone'};
+ my $error = $opts->{'error'};
+
+ if (!$entry_obj->visible_to($u)) {
+ $$error = LJ::Lang::ml('repost.access_denied');
+ return;
+ }
+
+ my $post_obj = __create_post($u, $timezone, $entry_obj->url);
+ if (!$post_obj) {
+ $$error = LJ::Lang::ml('repost.unknown_error');
+ return;
+ }
+
+ my $url = $entry_obj->url;
+ $post_obj->convert_to_repost($url);
+
+ # create record
+ my $repost_jitemid = $post_obj->jitemid;
+
+ my $journalid = $entry_obj->journalid;
+ my $jitemid = $entry_obj->jitemid;
+
+ __create_repost_record($entry_obj->journal,
+ $jitemid,
+ $u->userid,
+ $repost_jitemid);
+
+ return $post_obj;
+}
+
+sub get_status {
+ my ($class, $u, $entry_obj) = @_;
+
+ my $reposted = __get_repostid( $entry_obj->journal, $entry_obj->jitemid, $u->userid );
+ return { 'result' => {
+ 'count' => __get_count($entry_obj->journal, $entry_obj->jitemid),
+ 'reposted' => !!$reposted, },
+ };
+}
+
+sub delete {
+ my ($class, $u, $entry_obj) = @_;
+ my $repost_itemid = __get_repostid( $entry_obj->journal, $entry_obj->jitemid, $u->userid );
+
+ if ($repost_itemid) {
+ LJ::delete_entry($u, $repost_itemid, undef, undef);
+ __delete_repost_record($entry_obj->journal, $entry_obj->jitemid, $u->userid);
+
+ return { 'result' => 'OK' };
+ }
+
+ return LJ::API::Error->get_error('entry_not_found');
+}
+
+sub create {
+ my ($class, $u, $entry_obj, $timezone) = @_;
+ my $result = {};
+
+ if ($entry_obj->original_post) {
+ $entry_obj = $entry_obj->original_post;
+ }
+
+ my $journalid = $entry_obj->journalid;
+ my $jitemid = $entry_obj->jitemid;
+
+ my $repost_itemid = __get_repostid( $entry_obj->journal, $jitemid, $u->userid );
+
+ my $error;
+
+ if ($repost_itemid) {
+ $error = LJ::Lang::ml('repost.already_exist');
+ } else {
+ my $reposted_obj = __create_repost( {'u' => $u,
+ 'entry_obj' => $entry_obj,
+ 'timezone' => $timezone,
+ 'error' => \$error } );
+
+ if ($reposted_obj) {
+ my $count = __get_count($entry_obj->journal, $entry_obj->jitemid);
+ $result->{'result'} = { 'count' => $count };
+ } elsif (!$error) {
+ $error = LJ::Lang::ml('api.unknown');
+ }
+ }
+
+ if ($error) {
+ $result->{'error'} = { 'error_code' => -9000,
+ 'error_message' => $error };
+ }
+
+ return $result;
+}
+
+sub substitute_content {
+ my ($class, $entry_obj, $opts) = @_;
+
+ my $original_entry_obj = $entry_obj->original_post;
+ return unless $original_entry_obj;
+
+ if ($opts->{'anum'}) {
+ ${$opts->{'anum'}} = $original_entry_obj->anum;
+ }
+
+ if ($opts->{'cluster_id'}) {
+ ${$opts->{'cluster_id'}} = $original_entry_obj->journal->clusterid;
+ }
+
+ if ($opts->{'original_post_obj'}) {
+ ${$opts->{'original_post_obj'}}= $original_entry_obj;
+ }
+
+ if ($opts->{'repost_obj'}) {
+ ${$opts->{'repost_obj'}} = $entry_obj;
+ }
+
+ if ($opts->{'ditemid'}) {
+ ${$opts->{'ditemid'}} = $original_entry_obj->ditemid;
+ }
+
+ if ($opts->{'itemid'}) {
+ ${$opts->{'itemid'}} = $original_entry_obj->jitemid;
+ }
+
+ if ($opts->{'journalid'}) {
+ ${$opts->{'journalid'}} = $original_entry_obj->journalid;
+ }
+
+ if ($opts->{'journalu'}) {
+ ${$opts->{'journalu'}} = $original_entry_obj->journal;
+ }
+
+ if ($opts->{'posterid'}) {
+ ${$opts->{'posterid'}} = $original_entry_obj->posterid;
+ }
+
+ if ($opts->{'allowmask'}) {
+ ${$opts->{'allowmask'}} = $original_entry_obj->allowmask;
+ }
+
+ if ($opts->{'security'}) {
+ ${$opts->{'security'}} = $original_entry_obj->security;
+ }
+
+ if ($opts->{'eventtime'}) {
+ ${$opts->{'eventtime'}} = $original_entry_obj->eventtime_mysql;
+ }
+
+ if ($opts->{'event'}) {
+ my $remote = LJ::get_remote();
+ my $text_var = LJ::u_equals($remote, $entry_obj->poster) ? 'entry.reference.journal.owner' :
+ 'entry.reference.journal.guest';
+
+ my $event_text = $original_entry_obj->event_html;
+ my $event = LJ::Lang::ml($text_var,
+ { 'author' => LJ::ljuser2($original_entry_obj->poster),
+ 'reposter' => LJ::ljuser2($entry_obj->poster),
+ 'datetime' => $entry_obj->eventtime_mysql,
+ 'text' => $event_text, });
+
+ ${$opts->{'event'}} = $event;
+ }
+
+ if ($opts->{'event_friend'}) {
+ my $event_text = $original_entry_obj->event_html;
+ my $journal = $original_entry_obj->journal;
+
+ my $text_var = $journal->is_community ? 'entry.reference.friends.community' :
+ 'entry.reference.friends.journal';
+
+ my $event = LJ::Lang::ml($text_var,
+ { 'author' => LJ::ljuser2($original_entry_obj->poster),
+ 'community' => LJ::ljuser2($original_entry_obj->journal->user),
+ 'reposter' => LJ::ljuser2($entry_obj->poster),
+ 'text' => $event_text, });
+
+ ${$opts->{'event_friend'}} = $event;
+ }
+
+ if ($opts->{'subject_repost'}) {
+ my $subject_text = $original_entry_obj->subject_html;
+ my $repost_text = LJ::Lang::ml('entry.reference.subject');
+ $subject_text .= " ( $repost_text )";
+ ${$opts->{'subject_repost'}} = $subject_text;
+ }
+
+ if ($opts->{'subject'}) {
+ ${$opts->{'subject'}} = $original_entry_obj->subject_html;
+ }
+
+ if ($opts->{'reply_count'}) {
+ ${$opts->{'reply_count'}} = $original_entry_obj->reply_count;
+ }
+
+ return 1;
+}
+
+1;
Modified: trunk/cgi-bin/LJ/Entry.pm
===================================================================
--- trunk/cgi-bin/LJ/Entry.pm 2012-05-12 09:03:36 UTC (rev 21924)
+++ trunk/cgi-bin/LJ/Entry.pm 2012-05-12 09:05:55 UTC (rev 21925)
@@ -121,6 +121,11 @@
# save the singleton if it doesn't exist
$singletons{$journalid}->{$jitemid} = $self;
+ my $url = $self->prop('repost_link');
+ if ($url && ($url ne $self->url) ) {
+ $self->{'original_post_obj'} = LJ::Entry->new_from_url($url);
+ }
+
return $self;
}
@@ -1490,6 +1495,17 @@
return LJ::can_delete_journal_item(@_);
}
+sub convert_to_repost {
+ my ($class, $url) = @_;
+ $class->set_prop( 'repost_link' => $url);
+}
+
+sub original_post {
+ my ($class) = @_;
+
+ return $class->{'original_post_obj'};
+}
+
package LJ;
use Class::Autouse qw (
Added: trunk/cgi-bin/LJ/JSON/RPC/Item.pm
===================================================================
--- trunk/cgi-bin/LJ/JSON/RPC/Item.pm (rev 0)
+++ trunk/cgi-bin/LJ/JSON/RPC/Item.pm 2012-05-12 09:05:55 UTC (rev 21925)
@@ -0,0 +1,179 @@
+package LJ::JSON::RPC::Item;
+
+use strict;
+use warnings;
+
+use LJ::API::Error;
+
+#
+# json request and response jpc 2.0
+#
+
+use LJ::JSON;
+
+sub new {
+ my ($class, $uri, $data) = @_;
+
+ my $self = bless {}, $class;
+
+ if ($data->{'fatal'}) {
+ $self->{'fatal'} = $data->{'fatal'};
+ return $self;
+ }
+
+ my $jsonrpc = $data->{'jsonrpc'};
+ my $method = $data->{'method'};
+ my $params = $data->{'params'};
+
+ $self->{'fatal'} = { 'error_code' => -32602, 'error_message' => 'Invalid params' } unless $params;
+ $self->{'fatal'} = { 'error_code' => -32602, 'error_message' => 'Invalid params' } if ref $params eq 'ARRAY';
+ if ($self->{'fatal'}) {
+ return $self;
+ }
+
+ my $remote = LJ::get_remote;
+ my $auth = delete $params->{'auth'};
+ if (!$auth) {
+ $self->{'fatal'} = { 'error_code' => -12600, 'error_message' => 'Auth is missed' };
+ } else {
+ if (!LJ::Auth->check_ajax_auth_token($remote, $uri, 'auth_token' => $auth)) {
+ $self->{'fatal'} = { 'error_code' => -12600, 'error_message' => 'Auth failed' };
+ }
+ }
+ if ($self->{'fatal'}) {
+ return $self;
+ }
+
+
+ $self->{'fatal'} = { 'error_code' => -32600, 'error_message' => 'Invalid Request' } unless $method;
+ $self->{'fatal'} = { 'error_code' => -32600, 'error_message' => 'Invalid Request' } if !$jsonrpc || $jsonrpc ne '2.0';
+
+ if ($self->{'fatal'}) {
+ return $self;
+ }
+
+
+ $self->{'uri'} = $uri;
+ $self->{'method'} = $method;
+ $self->{'params'} = $params;
+ $self->{'id'} = $data->{'id'};
+
+ return $self,
+}
+
+
+#
+# When a rpc call encounters an error, the Response Object MUST contain
+# the error member with a value that is a Object with the following members:
+# - code
+# - message
+# - data
+#
+sub __construct_error_object {
+ LJ::API::Error->get_error
+}
+
+sub response {
+ my ($self, $options) = @_;
+
+ my $result = $options->{'result'};
+ my $error = $options->{'error'};
+ my $fatal = $self->{'fatal'};
+ return if ($self->is_notitification && !$fatal);
+
+ my $resp = { 'jsonrpc' => '2.0' };
+
+ if ($result && $error) {
+ # internal error
+ $fatal = { 'error_code' => -32603, 'error_message' => 'Internal error' };
+ }
+
+ ############################################################################
+ # result
+ ############################################################################
+ #
+ # This member is REQUIRED on success.
+ # This member MUST NOT exist if there was an error invoking the method.
+ # The value of this member is determined by the method invoked on the Server.
+ #
+ ############################################################################
+ if ($result) {
+ my $remote = LJ::get_remote;
+ my @params_vars = keys %{$self->{'params'}};
+ my $auth = LJ::Auth->ajax_auth_token($remote, $self->{'uri'}, \@params_vars);
+ $result->{'auth'} = $auth;
+ $resp->{'result'} = $result;
+ }
+
+ ############################################################################
+ # error
+ ############################################################################
+ #
+ # When a rpc call encounters an error, the Response Object MUST contain
+ # the error member with a value that is a Object with the following members:
+ # - code
+ # - message
+ # - data
+ #
+ #############################################################################
+ if ($error || $fatal) {
+ $error = $fatal if ($fatal);
+ my $error_information;
+
+ my $error_data;
+ if ($error->{'defined'}) {
+ my $error_type = $error->{'error_type'};
+ my $error_options = $error->{'error_options'};
+
+ $error = LJ::API::Error->get_error($error_type, $error_options)->{'error'};
+ }
+
+ my $error_code = $error->{'error_code'};
+ my $error_msg = $error->{'error_message'};
+
+ $error_data->{'code'} = $error_code;
+ $error_data->{'message'}= $error_msg;
+
+ if ($error->{'data'}) {
+ $error_data->{'data'} = $error->{'data'};
+ }
+
+ $resp->{'error'} = $error_data;
+ }
+
+ ##############################################################################
+ # id
+ ##############################################################################
+ #
+ # This member is REQUIRED.
+ # It MUST be the same as the value of the id member in the Request Object.
+ # If there was an error in detecting the id in the Request object
+ # (e.g. Parse error/Invalid Request), it MUST be Null.
+ #
+ ##############################################################################
+ $resp->{'id'} = $self->{'id'};
+
+ return $resp;
+}
+
+sub error {
+ my ($self) = @_;
+ return $self->{'fatal'};
+}
+
+sub method {
+ my ($self) = @_;
+ return $self->{'method'};
+}
+
+sub params {
+ my ($self) = @_;
+ return $self->{'params'};
+}
+
+sub is_notitification {
+ my ($self) = @_;
+ return !$self->{'id'};
+}
+
+1;
Added: trunk/cgi-bin/LJ/JSON/RPC.pm
===================================================================
--- trunk/cgi-bin/LJ/JSON/RPC.pm (rev 0)
+++ trunk/cgi-bin/LJ/JSON/RPC.pm 2012-05-12 09:05:55 UTC (rev 21925)
@@ -0,0 +1,86 @@
+package LJ::JSON::RPC;
+
+use strict;
+use warnings;
+
+use LJ::JSON;
+use LJ::JSON::RPC::Item;
+use LJ::Response::JSON;
+
+sub new {
+ my ($class, $uri, $request) = @_;
+ my $self = bless {}, $class;
+
+ eval {
+ my $data = LJ::JSON->from_json($request);
+
+ if (ref $data eq 'ARRAY') {
+ foreach my $entry (@$data) {
+ push @{$self->{'items'}}, { 'item' => LJ::JSON::RPC::Item->new($uri, $entry), };
+ }
+
+ unless (@$data) {
+ my $fatal = { 'error_code' => -32600, 'error_message' => 'Invalid Request' };
+ $self->{'items'} = { 'item' => LJ::JSON::RPC::Item->new({ 'fatal' => $fatal })};
+ }
+ } else {
+ $self->{'items'} = { 'item' => LJ::JSON::RPC::Item->new($uri, $data),};
+ }
+ };
+
+ if ($@) {
+ warn $@;
+ my $fatal = { 'error_code' => -32700, 'error_message' => 'Parse error' };
+ $self->{'items'} = { 'item' => LJ::JSON::RPC::Item->new({ 'fatal' => $fatal })};
+ }
+
+ return $self;
+}
+
+sub call {
+ my ($self, $callback) = @_;
+ my $items = $self->{'items'};
+
+ if (ref $items eq 'ARRAY') {
+ foreach my $entry (@$items) {
+ my $item = $entry->{'item'};
+ next if $item->error;
+
+ my $method = $item->method;
+ my $params = $item->params;
+
+ $entry->{'result'} = $callback->($method, $params);
+ }
+ } else {
+ my $item = $items->{'item'};
+ return if $item->error;
+
+ my $method = $item->method;
+ my $params = $item->params;
+
+ $items->{'result'} = $callback->($method, $params);
+ }
+}
+
+sub response {
+ my ($self) = @_;
+ my $items = $self->{'items'};
+ my $resp_data;
+
+ if (ref $items eq 'ARRAY') {
+ foreach my $entry (@$items) {
+ my $item = $entry->{'item'};
+ my $response = $item->response($entry->{'result'});
+ push @{$resp_data}, $response if $response;
+ }
+ } else {
+ my $item = $items->{'item'};
+ $resp_data = $item->response($items->{'result'});
+ }
+
+ my $response = LJ::Response::JSON->new();
+ $response->data($resp_data);
+ return $response;
+}
+
+1;
Added: trunk/cgi-bin/LJ/Router/API.pm
===================================================================
--- trunk/cgi-bin/LJ/Router/API.pm (rev 0)
+++ trunk/cgi-bin/LJ/Router/API.pm 2012-05-12 09:05:55 UTC (rev 21925)
@@ -0,0 +1,16 @@
+package LJ::Router::API;
+use strict;
+use warnings;
+
+use LJ::Controller::API::JSON;
+
+sub match_controller {
+ if ( LJ::Request->hostname eq $LJ::DOMAIN_WEB ) {
+ if ( LJ::Request->uri =~ m!^\/__api\/!ig and 'JSON_rpc' ) {
+ LJ::Request->notes( controller => 'LJ::Controller::API::JSON' );
+ return;
+ }
+ }
+}
+
+1;
Modified: trunk/cgi-bin/ljlib.pl
===================================================================
--- trunk/cgi-bin/ljlib.pl 2012-05-12 09:03:36 UTC (rev 21924)
+++ trunk/cgi-bin/ljlib.pl 2012-05-12 09:05:55 UTC (rev 21925)
@@ -106,6 +106,7 @@
"comet_history", "pingrel",
"eventrates", "eventratescounters",
"friending_actions_q", "delayedlog2", "delayedblob2",
+ "repost2",
);
# keep track of what db locks we have out
