Jelajahi Sumber

more #130: Data routes for posts/ look to work in dummy mode.

Still need to test flat-file mode, and kill the rest of the routes

Also need to hup the parent on post/add remove to update routes.
George S. Baugh 4 tahun lalu
induk
melakukan
a145c16f0b

+ 1 - 0
bin/build_index.pl

@@ -14,3 +14,4 @@ my $conf = Trog::Config::get();
 my $search = Trog::Data->new($conf);
 
 Trog::SQLite::TagIndex::build_index($search);
+Trog::SQLite::TagIndex::build_routes($search);

+ 0 - 8
data/DUMMY-dist.json

@@ -5,7 +5,6 @@
       "data" : "Here, caveman",
       "href" : "/",
       "id" : 665,
-      "local_href" : "/assets/today.html",
       "tags" : [
          "news",
          "public"
@@ -20,7 +19,6 @@
       "data" : "Is amazing",
       "href" : "/",
       "id" : 666,
-      "local_href" : "/assets/blog/Muh Blog.html",
       "tags" : [
          "blog",
          "public"
@@ -36,7 +34,6 @@
       "data" : "Vote for Nobody, nobody really cares!",
       "href" : "/",
       "id" : 669,
-      "local_href" : "/assets/about/Nobody.html",
       "preview" : "/img/avatar/humm.gif",
       "tags" : [
          "about",
@@ -53,7 +50,6 @@
       "data" : "Default avatar for new users",
       "href" : "/img/avatar/humm.gif",
       "id" : 420,
-      "local_href" : "/img/avatar/humm.gif",
       "preview" : "/img/avatar/humm.gif",
       "tags" : [
          "image",
@@ -71,7 +67,6 @@
       "data" : "Test Pattern",
       "href" : "/img/sys/testpattern.jpg",
       "id" : 90210,
-      "local_href" : "/img/sys/testpattern.jpg",
       "preview" : "/img/sys/testpattern.jpg",
       "tags" : [
          "image",
@@ -88,7 +83,6 @@
       "data" : "Test recording for tCMS",
       "href" : "/assets/audio/test.mp3",
       "id" : 111,
-      "local_href" : "/assets/audio/test.mp3",
       "preview" : "/img/sys/testpattern.jpg",
       "tags" : [
          "audio",
@@ -105,7 +99,6 @@
       "data" : "Test video for tCMS",
       "href" : "/assets/video/test.ogv",
       "id" : "222",
-      "local_href" : "/assets/video/test.ogv",
       "preview" : "/img/sys/testpattern.jpg",
       "tags" : [
          "video",
@@ -140,7 +133,6 @@
       "data" : "tCMS Logo",
       "href" : "/img/icon/tCMS.svg",
       "id" : 90211,
-      "local_href" : "/img/icon/tCMS.svg",
       "preview" : "/img/icon/tCMS.svg",
       "tags" : [
          "image",

+ 11 - 0
lib/TCMS.pm

@@ -23,14 +23,25 @@ use IO::Compress::Deflate();
 use lib 'lib';
 use Trog::Routes::HTML;
 use Trog::Routes::JSON;
+
 use Trog::Auth;
 use Trog::Utils;
+use Trog::Config;
+use Trog::Data;
 
 # Troglodyne philosophy - simple as possible
 
 # Import the routes
+
+my $conf = Trog::Config::get();
+my $data = Trog::Data->new($conf);
+my %roots = $data->routes();
+use Data::Dumper;
+print Dumper(\%roots);
+
 my %routes = %Trog::Routes::HTML::routes;
 @routes{keys(%Trog::Routes::JSON::routes)} = values(%Trog::Routes::JSON::routes);
+@routes{keys(%roots)} = values(%roots);
 
 #1MB chunks
 my $CHUNK_SIZE = 1024000;

+ 4 - 0
lib/Trog/Data/FlatFile.pm

@@ -82,6 +82,10 @@ sub _index ($self) {
     return sort { $b cmp $a } @index;
 }
 
+sub routes ($self) {
+    return Trog::SQLite::TagIndex::routes();
+}
+
 sub write($self,$data) {
     foreach my $post (@$data) {
         my $file = "$datastore/$post->{id}";

+ 19 - 1
lib/Trog/DataModule.pm

@@ -94,12 +94,18 @@ sub _fixup ($self, @filtered) {
     # Finally, add visibility
     @filtered = _add_visibility(@filtered);
 
-    #urlencode spaces in filenames
+    # urlencode spaces in filenames
     @filtered = map {
         foreach my $param (qw{href preview video_href audio_href local_href wallpaper}) {
             next unless exists $_->{$param};
             $_->{$param} =~ s/ /%20/g;
         }
+
+        #Add routing data for posts which don't have them (/posts/$id)
+        $_->{local_href} = "/posts/$_->{id}"                unless exists($_->{local_href});
+        $_->{method}     = 'GET'                       unless exists($_->{method});
+        $_->{callback}   = "Trog::Routes::HTML::posts" unless exists($_->{callback});
+
         $_
     } @filtered;
 
@@ -320,4 +326,16 @@ You should override this, it is a stub here.
 
 sub delete ($self) { die 'stub' }
 
+=head2 routes()
+
+Returns the routes to each post.
+You should override this for performance reasons, as it's just a wrapper around get() by defualt.
+
+=cut
+
+sub routes($self) {
+    my %routes = map { $_->{local_href} => { method => $_->{method}, callback => \&{$_->{callback}} } } ($self->get( limit => 0, acls => ['admin'] ));
+    return %routes;
+}
+
 1;

+ 33 - 30
lib/Trog/Routes/HTML.pm

@@ -66,20 +66,16 @@ our %routes = (
         nostatic => 1,
         callback => \&Trog::Routes::HTML::login,
     },
-    '/config' => {
+    '/post' => {
         method   => 'GET',
         auth     => 1,
-        callback => \&Trog::Routes::HTML::config,
-    },
-    '/config/save' => {
-        method   => 'POST',
-        auth     => 1,
-        callback => \&Trog::Routes::HTML::config_save,
+        callback => \&Trog::Routes::HTML::post,
     },
-    '/post' => {
+    '/post/(.*)' => {
         method   => 'GET',
         auth     => 1,
         callback => \&Trog::Routes::HTML::post,
+        captures => ['id'],
     },
     '/post/save' => {
         method   => 'POST',
@@ -91,31 +87,13 @@ our %routes = (
         auth     => 1,
         callback => \&Trog::Routes::HTML::post_delete,
     },
-    '/post/(.*)' => {
-        method   => 'GET',
-        auth     => 1,
-        callback => \&Trog::Routes::HTML::post,
-        captures => ['id'],
-    },
-    '/posts/(.*)' => {
-        method   => 'GET',
-        callback => \&Trog::Routes::HTML::posts,
-        captures => ['id'],
-    },
-    '/posts' => {
-        method   => 'GET',
-        callback => \&Trog::Routes::HTML::posts,
-    },
-    '/profile' => {
-        method   => 'POST',
-        auth     => 1,
-        callback => \&Trog::Routes::HTML::profile,
-    },
     '/themeclone' => {
         method   => 'POST',
         auth     => 1,
         callback => \&Trog::Routes::HTML::themeclone,
     },
+
+    # Can also be made into posts
     '/sitemap', => {
         method   => 'GET',
         callback => \&Trog::Routes::HTML::sitemap,
@@ -166,6 +144,27 @@ our %routes = (
         callback => \&Trog::Routes::HTML::avatars,
         data     => { tag => ['about'] },
     },
+
+    #TODO make all these routes dynamic from data
+    '/config' => {
+        method   => 'GET',
+        auth     => 1,
+        callback => \&Trog::Routes::HTML::config,
+    },
+    '/config/save' => {
+        method   => 'POST',
+        auth     => 1,
+        callback => \&Trog::Routes::HTML::config_save,
+    },
+    '/posts' => {
+        method   => 'GET',
+        callback => \&Trog::Routes::HTML::posts,
+    },
+    '/profile' => {
+        method   => 'POST',
+        auth     => 1,
+        callback => \&Trog::Routes::HTML::profile,
+    },
     '/users/(.*)' => {
         method => 'GET',
         callback => \&Trog::Routes::HTML::users,
@@ -198,7 +197,6 @@ $routes{'/post/about'}   = { method => 'GET', auth => 1, callback => \&Trog::Rou
 $routes{'/post/series'}  = { method => 'GET', auth => 1, callback => \&Trog::Routes::HTML::post, data => { tag => ['series'],  type => 'series'    } };
 
 # Build aliases for /posts/(.*) and /post/(.*) with extra data
-@routes{map { "/$_/(.*)" } @post_aliases} = map { my %copy = %{$routes{'/posts/(.*)'}}; \%copy } @post_aliases;
 @routes{map { "/post/$_/(.*)" } @post_aliases} = map { my %copy = %{$routes{'/post/(.*)'}}; \%copy } @post_aliases;
 
 # /series/$ID is a bit of a special case, it's actuallly gonna need special processing
@@ -529,7 +527,7 @@ sub _get_series($edit=0,$search_info=0) {
         limit   => 10,
         page    => 1,
     );
-    @series = map { $_->{href} = "/post$_->{href}"; $_ } @series if $edit;
+    @series = map { $_->{local_href} = "/post$_->{local_href}"; $_ } @series if $edit;
     return @series;
 }
 
@@ -778,7 +776,12 @@ Display multi or single posts, supports RSS and pagination.
 =cut
 
 sub posts ($query, $render_cb) {
+    #Process the input URI to capture tag/id
+    my (undef, $tag, $id) = split(/\//, $query->{route});
+
     my $tags = _coerce_array($query->{tag});
+    push(@$tags, $tag) if $tag && $tag ne 'posts';
+    $query->{id} = $id if $id;
 
     push(@{$query->{acls}}, 'public');
     push(@{$query->{acls}}, 'unlisted') if $query->{id};

+ 35 - 0
lib/Trog/SQLite/TagIndex.pm

@@ -14,6 +14,8 @@ use Trog::SQLite;
 An SQLite3 index of posts by tag.
 Used to speed up the flat-file data model.
 
+Also used to retrieve cached routes from posts.
+
 =head1 FUNCTIONS
 
 =cut
@@ -26,13 +28,23 @@ sub posts_for_tags (@tags) {
     return map { $_->{id} } @$rows;
 }
 
+sub routes {
+    my $dbh = _dbh();
+    my $rows = $dbh->selectall_arrayref("SELECT route, method, callback FROM all_routes",{ Slice => {} });
+    return () unless ref $rows eq 'ARRAY' && @$rows;
+    my %routes = map { $_->{route} => { method => $_->{method}, callback => $_->{callback} } } @$rows;
+    return %routes;
+}
+
 sub add_post ($post,$data_obj) {
     my $dbh = _dbh();
+    build_routes($data_obj,[$post]);
     return build_index($data_obj,[$post]);
 }
 
 sub remove_post ($post) {
     my $dbh = _dbh();
+    $dbh->do("DELETE FROM routes WHERE route=?", undef, $post->{local_href});
     return $dbh->do("DELETE FROM posts_index WHERE post_id=?", undef, $post->{id});
 }
 
@@ -51,6 +63,29 @@ sub build_index($data_obj,$posts=[]) {
     } @$posts );
 }
 
+# It is important we use get() instead of read() because of incomplete data.
+sub build_routes($data_obj,$posts=[]) {
+    my $dbh = _dbh();
+    $posts = $data_obj->get({ limit => 0, acls => ['admin'] }) unless @$posts;
+
+    # Ensure the methods & callbacks we need are installed
+    Trog::SQLite::bulk_insert($dbh,'methods',   [qw{method}],   'IGNORE', (uniq map { $_->{method} }   @posts) ); 
+    Trog::SQLite::bulk_insert($dbh,'callbacks', [qw{callback}], 'IGNORE', (uniq map { $_->{callback} } @posts) ); 
+
+    my $m = $dbh->selectall_hashref("SELECT id, method FROM methods", 'method');
+    foreach my $k (keys(%$m)) { $m->{$k} = $m->{$k}->{id} };
+    my $c = $dbh->selectall_hashref("SELECT id, callback FROM callbacks", 'callback');
+    foreach my $k (keys(%$c)) { $c->{$k} = $c->{$k}->{id} };
+    @$posts = map {
+        $_->{method_id}   = $m->{$_->{method}};
+        $_->{callback_id} = $c->{$_->{callback}};
+        $_
+    } @$posts;
+
+    my @routes = map { ($_->{local_href}, $_->{method_id}, $_->{callback_id} ) } @$posts;
+    Trog::SQLite::bulk_insert($dbh,'routes', [qw{route method_id callback_id}], 'IGNORE', @routes); 
+}
+
 # Ensure the db schema is OK, and give us a handle
 sub _dbh {
     my $file   = 'schema/flatfile.schema';

+ 23 - 0
schema/flatfile.schema

@@ -11,3 +11,26 @@ CREATE TABLE IF NOT EXISTS posts_index (
 CREATE INDEX IF NOT EXISTS tag_idx ON tag(name);
 
 CREATE VIEW IF NOT EXISTS posts AS SELECT p.post_id as id, t.name AS tag FROM posts_index AS p JOIN tag AS t ON t.id=p.tag_id;
+
+/* The intention is to read this entirely into memory at app startup     */
+/* This should not incur significant costs, even with millions of posts. */
+CREATE TABLE IF NOT EXISTS routes (
+    route TEXT NOT NULL UNIQUE,
+    method_id TEXT NOT NULL REFERENCES methods(id) ON DELETE RESTRICT,
+    callback_id TEXT NOT NULL REFERENCES callbacks(id) ON DELETE RESTRICT
+);
+
+/* Enum tables like this always require cleanup when there are no more references. */
+/* TODO  ^^^ */
+CREATE TABLE IF NOT EXISTS methods (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    method TEXT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS callbacks (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    callback TEXT NOT NULL
+);
+
+CREATE VIEW IF NOT EXISTS all_routes AS SELECT r.route AS route, m.method AS method, c.callback AS callback FROM routes AS r JOIN methods AS m ON m.id=r.method_id JOIN callbacks AS c ON c.id=r.callback_id;
+

+ 1 - 1
www/templates/categories.tx

@@ -1,3 +1,3 @@
 : for $categories -> $category { 
-<a href="<: $category.href :>"   title="<: $category.title :>" class="topbar"><: $category.title :></a>
+<a href="<: $category.local_href :>"   title="<: $category.title :>" class="topbar"><: $category.title :></a>
 : }