commit 2c588688550d7cac53ba44ea2a1dc86ea46f1beb Author: Alex 'AdUser' Z Date: Mon Aug 21 22:51:30 2017 +1000 * initial diff --git a/bin/cmtd b/bin/cmtd new file mode 100755 index 0000000..6579977 --- /dev/null +++ b/bin/cmtd @@ -0,0 +1,11 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use FindBin; +BEGIN { unshift @INC, "$FindBin::Bin/../lib" } + +# Start command line interface for application +require Mojolicious::Commands; +Mojolicious::Commands->start_app('CMTD'); diff --git a/cmtd.conf b/cmtd.conf new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/cmtd.conf @@ -0,0 +1 @@ +{} diff --git a/lib/CMTD.pm b/lib/CMTD.pm new file mode 100644 index 0000000..b1a74cb --- /dev/null +++ b/lib/CMTD.pm @@ -0,0 +1,29 @@ +package CMTD; + +use strict; +use warnings; + +use Mojo::Base 'Mojolicious'; + +sub startup { + my $self = shift; + + $self->plugin('CMTD::Helpers'); + $self->plugin(Config => {file => 'cmtd.conf'}); + + my $r = $self->routes; + + $r->get('/') + -> to('main#index'); + + $r->get('/captcha') + -> to('main#captcha'); + + $r->get('/comments/list') + -> to('main#c_list'); + + $r->get('/comments/add') + -> to('main#c_add'); +} + +1; diff --git a/lib/CMTD/Comments.pm b/lib/CMTD/Comments.pm new file mode 100644 index 0000000..f16fad2 --- /dev/null +++ b/lib/CMTD/Comments.pm @@ -0,0 +1,105 @@ +package LDV::Comments; + +use strict; +use warnings; +use utf8; + +use Mojo::Base 'Mojolicious::Controller'; + +use File::Slurp qw(read_file write_file); +use Mojo::URL; + +sub add { + my ($self) = @_; + + eval { + my $sectoken = $self->session('c_sectoken') + or die("missing security token\n"); + my ($ip, $upto) = ($sectoken =~ m{^([0-9a-f\.:]+)-(\d+)$}io) + or die("malformed security token\n"); + ($upto > time()) + or die("expired security token\n"); + ($ip eq $self->tx->remote_address) + or die("remote address mismatch\n"); + my $text = $self->req->param('text') + or die("empty comment\n"); + my $pageid = $self->_gen_pageid() + or die("can't get pageid\n"); + + my %opts = (binmode => ':bytes'); + my $comments = []; + my $path = $self->app->home->rel_file("data/comments/$pageid.json"); + if (-f $path) { + my $json = read_file($path, %opts); + $comments = $self->app->json->decode($json); + } + push @{ $comments }, { + text => $text, time => time(), + user => $self->session('username') || 'anonymous', + }; + write_file($path, \%opts, $self->app->json->encode($comments)); + + $path = $self->app->home->rel_file("data/comments/$pageid.html"); + $self->stash({comments => $comments}); + write_file($path, {binmode => ':utf8'}, $self->render_to_string(template => 'comments/list')); + + $self->render(text => 'OK'); + } or do { + chomp $@; + $self->app->log->error($@); + $@ = 'internal error' if $@ =~ m{line \d+}o; + $self->res->code(400); + $self->render(text => $@); + }; + + $self->rendered(); +} + +sub get { + my ($self) = @_; + + eval { + my $pageid = $self->_gen_pageid() + or die("can't get id\n"); + my $path = $self->app->home->rel_file("data/comments/$pageid.html"); + if (-f $path) { + my $comments = read_file($path, binmode => ':utf8'); + $self->render(text => $comments); + } else { + $self->render(template => 'comments/none'); + } 1; + } or do { + chomp $@; + $self->app->log->error($@); + $@ = 'internal error' if $@ =~ m{line \d+}o; + $self->res->code(400); + $self->render(text => $@); + }; + + $self->rendered(); +} + +sub create { + my ($self) = @_; + + eval { + die("request error\n") + unless $self->req->is_xhr; + my $ip = $self->tx->remote_address + or die("can't find remote ip\n"); + my $sectoken = sprintf "%s-%d", $ip, time() + 60 * 7; + $self->session(c_sectoken => $sectoken); + my $pageid = $self->_gen_pageid() + or die("can't get pageid\n"); + $self->stash({pageid => $pageid}); + $self->render(template => 'comments/new'); + } or do { + chomp $@; + $self->app->log->error($@); + $@ = 'internal error' if $@ =~ m{line \d+}o; + $self->res->code(400); + $self->render(text => $@); + }; +} + +1; diff --git a/lib/CMTD/Helpers.pm b/lib/CMTD/Helpers.pm new file mode 100644 index 0000000..b1d20cb --- /dev/null +++ b/lib/CMTD/Helpers.pm @@ -0,0 +1,36 @@ +package CMTD::Helpers; + +use strict; +use warnings; +use utf8; + +use Mojo::Base 'Mojolicious::Plugin'; + +sub register { + my ($self, $app) = @_; + + $app->helper(referrer => sub { + my ($c, $url) = @_; + my $url = $c->req->headers->referrer || + $c->req->param('url'); + return $url; + }); + + $app->helper(pageid => sub { + my ($c, $url) = @_; + return unless $url; + + my $u = Mojo::URL->new($url); + my $site = $u->host; + my $path = $u->path; + $path =~ s{^/+}{}o; + $path =~ s{/+$}{}o; + $path =~ y{/.}{-}s; + $path =~ s<\.[a-z0-9]{2,4}$><>io; + my $md5 = md5_sum($path); + + return {site => $site, pid => $md5, path => $path}; + }); +} + +1; diff --git a/lib/CMTD/Main.pm b/lib/CMTD/Main.pm new file mode 100644 index 0000000..96fe9ca --- /dev/null +++ b/lib/CMTD/Main.pm @@ -0,0 +1,47 @@ +package CMTD::Main; + +use strict; +use warnings; +use utf8; + +use Mojo::Base 'Mojolicious::Controller'; + +sub index { + my $self = shift; + + $self->render(text => 'Go away!'); +} + +sub captcha { +} + +sub c_list { +} + +sub c_add { + my ($self) = @_; + + unless (my $ref = $self->referrer and ref $ref eq 'HASH') { + $self->res->code(400); + $self->render(text => "Can't detect referred page"); + return; + } + + unless (my $site = $self->app->sites->{ $ref->{site} }) { + $self->res->code(400); + $self->render(text => "No such site"); + return; + } + + eval { + } or do { + chomp $@; + my $msg = sprintf "Error when listing comments for %s/%s: %s", + $ref->{site}, $ref->{pid}, $@; + $self->app->log->error($msg); + $self->res->code(500); + $self->render(text => 'Internal error'); + }; +} + +1; diff --git a/lib/LDV.pm b/lib/LDV.pm new file mode 100644 index 0000000..37e9a56 --- /dev/null +++ b/lib/LDV.pm @@ -0,0 +1,94 @@ +package LDV; + +use strict; +use warnings; +use utf8; + +use Mojo::Base 'Mojolicious'; + +sub startup { + my ($self) = @_; + + my $config = $self->app->home->rel_file('conf/ldv.conf'); + $self->plugin(Config => {file => $config}); + $self->plugin(I18N => {default => 'ru'}); + $self->plugin('LDV::Helpers'); + + $self->app->mode('production'); + $self->app->secrets([ $self->app->config->{secret} ]); + + $self->app->attr(json => sub { + require JSON; + my $json = JSON->new->utf8; + return $json; + }); + + $self->app->attr(email => sub { + require LDV::Email; + my $email = LDV::Email->new($self->app->config->{email} // {}); + return $email; + }); + + $self->app->attr(ldap => sub { + require LDV::LDAP; + my $ldap = LDV::LDAP->new($self->app->config->{ldap}); + return $ldap; + }); + + my $r = $self->routes; + + { # /comments + my $comm = $r->route('/comments') -> to(controller => 'comments'); + $comm->post('/add') ->to(action => 'add'); + $comm->get ('/get') ->to(action => 'get'); + $comm->get ('/new') ->to(action => 'create'); + + mkdir $self->app->home->rel_dir('data/comments'); + } + + { # /user + my $user = $r->route('/user') -> to(controller => 'user'); + $user->get('/') ->to(cb => sub { shift->redirect_to('/user/login'); }); + $user->get('/login') ->to(action => 'login'); + $user->get('/register') ->to(action => 'register'); + $user->get('/profile') ->to(action => 'profile'); + + $user->post('/auth') ->to(action => 'auth'); + $user->get ('/logout') ->to(action => 'logout'); + $user->post('/create') ->to(action => 'create'); + $user->post('/update') ->to(action => 'update'); + } + + { # /zerobin + my $zb = $r->route('/zerobin2') -> to(controller => 'zerobin'); + $zb->post('/') -> to(action => 'save'); + $zb->get ('/') -> to(action => 'create'); + $zb->route('/:time', time => qr/\d+/) + ->via('GET') -> to(action => 'view'); + $zb->get('/prune') -> to(action => 'prune'); + + my $conf = $self->app->config->{zerobin}; + mkdir $self->app->home->rel_dir($conf->{root}); + } + + { # /imgbin + my $conf = $self->app->config->{imgbin}; + my $ib = $r->route('/imgbin') -> to(controller => 'imgbin'); + $ib->post('/') -> to(action => 'save'); + $ib->get ('/') -> to(action => 'create'); + $ib->route('/:time', time => qr/\d+/) + ->via('GET') -> to(action => 'view'); + $ib->get ('/prune') -> to(action => 'prune'); + $ib->get ('/latest') -> to(action => 'latest') + if ($conf->{show_latest}); + + mkdir $self->app->home->rel_dir($conf->{root}); + mkdir $self->app->home->rel_file('public/images'); + mkdir $self->app->home->rel_file('public/images/full'); + mkdir $self->app->home->rel_file('public/images/small'); + + $ENV{MOJO_MAX_MESSAGE_SIZE} = $conf->{maxsize} + 2 * 1024 * 1024; # +2Mb + } +} + +1; diff --git a/t/basic.t b/t/basic.t new file mode 100644 index 0000000..f00e95c --- /dev/null +++ b/t/basic.t @@ -0,0 +1,9 @@ +use Mojo::Base -strict; + +use Test::More; +use Test::Mojo; + +my $t = Test::Mojo->new('CMTD'); +$t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i); + +done_testing();