ソースを参照

More #130: Bootstrapping from nothing is working

George S. Baugh 4 年 前
コミット
834bfa114f

+ 0 - 354
data/DUMMY-dist.json

@@ -1,354 +0,0 @@
-[
-   {
-      "content_type" : "text/html",
-      "created" : 1604424769,
-      "data" : "Here, caveman",
-      "href" : "/",
-      "id" : 665,
-      "tags" : [
-         "news",
-         "public"
-      ],
-      "title" : "Example Post",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "content_type" : "text/html",
-      "created" : 1604424769,
-      "data" : "Is amazing",
-      "href" : "/",
-      "id" : 666,
-      "tags" : [
-         "blog",
-         "public"
-      ],
-      "title" : "Muh Blog",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "background_image" : "/img/sys/testpattern.jpg",
-      "content_type" : "text/html",
-      "created" : 1604424769,
-      "data" : "Vote for Nobody, nobody really cares!",
-      "href" : "/",
-      "id" : 669,
-      "preview" : "/img/avatar/humm.gif",
-      "tags" : [
-         "about",
-         "profile",
-         "public"
-      ],
-      "title" : "Nobody",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "content_type" : "image/gif",
-      "created" : 1604424769,
-      "data" : "Default avatar for new users",
-      "href" : "/img/avatar/humm.gif",
-      "id" : 420,
-      "preview" : "/img/avatar/humm.gif",
-      "tags" : [
-         "image",
-         "files",
-         "profile-image",
-         "public"
-      ],
-      "title" : "humm.gif",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "content_type" : "image/jpeg",
-      "created" : 1604424769,
-      "data" : "Test Pattern",
-      "href" : "/img/sys/testpattern.jpg",
-      "id" : 90210,
-      "preview" : "/img/sys/testpattern.jpg",
-      "tags" : [
-         "image",
-         "files",
-         "public"
-      ],
-      "title" : "testpattern.jpg",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "content_type" : "audio/mpeg",
-      "created" : 1604424769,
-      "data" : "Test recording for tCMS",
-      "href" : "/assets/audio/test.mp3",
-      "id" : 111,
-      "preview" : "/img/sys/testpattern.jpg",
-      "tags" : [
-         "audio",
-         "files",
-         "public"
-      ],
-      "title" : "test.mp3",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "content_type" : "video/ogg",
-      "created" : 1604424769,
-      "data" : "Test video for tCMS",
-      "href" : "/assets/video/test.ogv",
-      "id" : "222",
-      "preview" : "/img/sys/testpattern.jpg",
-      "tags" : [
-         "video",
-         "files",
-         "public"
-      ],
-      "title" : "test.ogv",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "aclname" : "admin",
-      "content_type" : "text/plain",
-      "created" : 1604424769,
-      "data" : "",
-      "href" : "/config",
-      "id" : "900",
-      "local_href" : "/config",
-      "preview" : "/img/sys/testpattern.jpg",
-      "tags" : [
-         "private",
-         "admin"
-      ],
-      "callback" : "Trog::Routes::HTML::config",
-      "title" : "Configure tCMS",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "aclname" : "admin",
-      "content_type" : "text/plain",
-      "created" : 1604424769,
-      "data" : "",
-      "href" : "/config/save",
-      "id" : "901",
-      "local_href" : "/config/save",
-      "preview" : "/img/sys/testpattern.jpg",
-      "tags" : [
-         "private",
-         "admin"
-      ],
-      "callback" : "Trog::Routes::HTML::config_save",
-      "title" : "Configure tCMS",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "content_type" : "image/svg",
-      "created" : 1604424769,
-      "data" : "tCMS Logo",
-      "href" : "/img/icon/tCMS.svg",
-      "id" : 90211,
-      "preview" : "/img/icon/tCMS.svg",
-      "tags" : [
-         "image",
-         "files",
-         "admin"
-      ],
-      "title" : "tCMS.svg",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "acls" : [
-         "admin"
-      ],
-      "audio_href" : "",
-      "content_type" : null,
-      "created" : 1604424769,
-      "data" : "Here, cavemanzzz",
-      "href" : "/",
-      "id" : "665",
-      "tags" : [
-         "news",
-         "public"
-      ],
-      "title" : "Example Post",
-      "user" : "doge",
-      "version" : 1,
-      "video_href" : ""
-   },
-   {
-      "acls" : [
-         "admin"
-      ],
-      "audio_href" : "",
-      "content_type" : null,
-      "created" : 1604424769,
-      "data" : "Here, cavemanzzzsdfgsdf <: embed(90211,'media') :>",
-      "href" : "/",
-      "id" : "665",
-      "tags" : [
-         "news",
-         "public"
-      ],
-      "title" : "Example Post",
-      "user" : "doge",
-      "version" : 2,
-      "video_href" : ""
-   },
-   {
-      "aclname" : "news",
-      "content_type" : "text/plain",
-      "created" : 1604424769,
-      "data" : "Microblog",
-      "href" : "/news",
-      "id" : "999",
-      "local_href" : "/news",
-      "preview" : "/img/sys/testpattern.jpg",
-      "tags" : [
-         "series",
-         "topbar",
-         "public"
-      ],
-      "callback" : "Trog::Routes::HTML::series",
-      "title" : "News",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "aclname" : "blog",
-      "content_type" : "text/plain",
-      "created" : 1604424769,
-      "data" : "Blog",
-      "href" : "/blog",
-      "id" : "9991",
-      "local_href" : "/blog",
-      "preview" : "/img/sys/testpattern.jpg",
-      "tags" : [
-         "series",
-         "topbar",
-         "public"
-      ],
-      "callback" : "Trog::Routes::HTML::series",
-      "title" : "Blog",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "aclname" : "image",
-      "content_type" : "text/plain",
-      "created" : 1604424769,
-      "data" : "Images",
-      "href" : "/image",
-      "id" : "9992",
-      "local_href" : "/image",
-      "preview" : "/img/sys/testpattern.jpg",
-      "tags" : [
-         "series",
-         "topbar",
-         "public"
-      ],
-      "callback" : "Trog::Routes::HTML::series",
-      "title" : "Images",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "aclname" : "video",
-      "content_type" : "text/plain",
-      "created" : 1604424769,
-      "data" : "Video",
-      "href" : "/video",
-      "id" : "9993",
-      "local_href" : "/video",
-      "preview" : "/img/sys/testpattern.jpg",
-      "tags" : [
-         "series",
-         "topbar",
-         "public"
-      ],
-      "callback" : "Trog::Routes::HTML::series",
-      "title" : "Videos",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "aclname" : "audio",
-      "content_type" : "text/plain",
-      "created" : 1604424769,
-      "data" : "Audio",
-      "href" : "/audio",
-      "id" : "9994",
-      "local_href" : "/audio",
-      "preview" : "/img/sys/testpattern.jpg",
-      "tags" : [
-         "series",
-         "topbar",
-         "public"
-      ],
-      "callback" : "Trog::Routes::HTML::series",
-      "title" : "Audio",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "aclname" : "files",
-      "content_type" : "text/plain",
-      "created" : 1604424769,
-      "data" : "Files",
-      "href" : "/files",
-      "id" : "9995",
-      "local_href" : "/files",
-      "preview" : "/img/sys/testpattern.jpg",
-      "tags" : [
-         "series",
-         "topbar",
-         "public"
-      ],
-      "callback" : "Trog::Routes::HTML::series",
-      "title" : "Files",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "aclname" : "series",
-      "content_type" : "text/plain",
-      "created" : 1604424769,
-      "data" : "Series",
-      "href" : "/series",
-      "id" : "9996",
-      "local_href" : "/series",
-      "preview" : "/img/sys/testpattern.jpg",
-      "tags" : [
-         "series",
-         "topbar",
-         "public"
-      ],
-      "callback" : "Trog::Routes::HTML::series",
-      "title" : "Series",
-      "user" : "Nobody",
-      "version" : 0
-   },
-   {
-      "aclname" : "about",
-      "content_type" : "text/plain",
-      "created" : 1604424769,
-      "data" : "About",
-      "href" : "/about",
-      "id" : "9997",
-      "local_href" : "/about",
-      "preview" : "/img/sys/testpattern.jpg",
-      "tags" : [
-         "series",
-         "topbar",
-         "public"
-      ],
-      "callback" : "Trog::Routes::HTML::series",
-      "title" : "About",
-      "user" : "Nobody",
-      "version" : 0
-   }
-]

+ 5 - 1
lib/Trog/Data/DUMMY.pm

@@ -24,7 +24,11 @@ sub help { 'https://perldoc.perl.org/functions/quotemeta.html' }
 our $posts;
 
 sub read ($self, $query={}) {
-    confess "Can't find datastore!" unless -f $datastore;
+    if ( !-f $datastore) {
+        open(my $fh, '>', $datastore);
+        print $fh '[]';
+        close $fh;
+    }
     my $slurped = File::Slurper::read_text($datastore);
     $posts = JSON::MaybeXS::decode_json($slurped);
 

+ 15 - 6
lib/Trog/DataModule.pm

@@ -149,17 +149,20 @@ sub filter ($self, $query, @filtered) {
 
     # Filter posts not matching the passed tag(s), if any
     @filtered = grep {
-        my $tags = $_->{tags}; grep { my $t = $_; grep { $t eq $_ } @{$query->{tags}} } @$tags
+        my $tags = $_->{tags};
+        grep { my $t = $_; grep { $t eq $_ } @{$query->{tags}} } @$tags
     } @filtered if @{$query->{tags}};
 
     # Filter posts *matching* the passed exclude_tag(s), if any
     @filtered = grep {
-        my $tags = $_->{tags}; !grep { my $t = $_; grep { $t eq $_ } @{$query->{exclude_tags}} } @$tags
+        my $tags = $_->{tags};
+        !grep { my $t = $_; grep { $t eq $_ } @{$query->{exclude_tags}} } @$tags
     } @filtered if @{$query->{exclude_tags}};
 
     # Filter posts without the proper ACLs
     @filtered = grep {
-        my $tags = $_->{tags}; grep { my $t = $_; grep { $t eq $_ } @{$query->{acls}} } @$tags
+        my $tags = $_->{tags};
+        grep { my $t = $_; grep { $t eq $_ } @{$query->{acls}} } @$tags
     } @filtered unless grep { $_ eq 'admin' } @{$query->{acls}};
 
     @filtered = grep { $_->{title} =~ m/\Q$query->{like}\E/i || $_->{data} =~ m/\Q$query->{like}\E/i } @filtered if $query->{like};
@@ -259,7 +262,7 @@ sub add ($self, @posts) {
     foreach my $post (@posts) {
         $post->{id} //= UUID::Tiny::create_uuid_as_string(UUID::Tiny::UUID_V1, UUID::Tiny::UUID_NS_DNS);
         # Generate a local_href so we can build the route correctly and not have to rely on auto-insert in _fixup() someday
-        $post->{local_href} = "/posts/$post->{id}";
+        $post->{local_href} //= "/posts/$post->{id}";
         $post->{created} = time();
         my @existing_posts = $self->get( id => $post->{id} );
         if (@existing_posts) {
@@ -273,6 +276,11 @@ sub add ($self, @posts) {
         push @to_write, $post;
     }
     $self->write(\@to_write);
+
+    #hup the parent to refresh the routing table
+    my $parent = getppid;
+    kill 'HUP', $parent;
+
     return 0;
 }
 
@@ -283,7 +291,7 @@ sub _process ($post) {
     $post->{href}      = _handle_upload($post->{file}, $post->{id})             if $post->{file};
     $post->{preview}   = _handle_upload($post->{preview_file}, $post->{id})     if $post->{preview_file};
     $post->{wallpaper} = _handle_upload($post->{wallpaper_file}, $post->{id})   if $post->{wallpaper_file};
-    $post->{preview} = $post->{href} if $post->{app} eq 'image';
+    $post->{preview}   = $post->{href} if $post->{app} && $post->{app} eq 'image';
     delete $post->{app};
     delete $post->{file};
     delete $post->{preview_file};
@@ -296,7 +304,8 @@ sub _process ($post) {
     # Handle acls/tags
     $post->{tags} //= [];
     @{$post->{tags}} = grep { my $subj = $_; !grep { $_ eq $subj} qw{public private unlisted} } @{$post->{tags}};
-    push(@{$post->{tags}}, delete $post->{acls}) if $post->{visibility} eq 'private';
+    push(@{$post->{tags}}, @{$post->{acls}}) if $post->{visibility} eq 'private';
+    delete $post->{acls};
     push(@{$post->{tags}}, delete $post->{visibility});
 
     # Add the 'series' tag if we are in a series, restrict to relevant acl

+ 85 - 12
lib/Trog/Routes/HTML.pm

@@ -394,7 +394,28 @@ sub login ($query, $render_cb) {
         if (!$hasusers) {
             # Make the first user
             Trog::Auth::useradd($query->{username}, $query->{password}, ['admin'] );
+            # Add a stub user page and the initial series.
+            my $dat = Trog::Data->new($conf);
+            _setup_initial_db($dat,$query->{username});
+            $dat->add({
+                title      => $query->{username},
+                data       => 'Default user',
+                preview    => '/img/avatar/humm.gif',
+                wallpaper  => '/img/sys/testpattern.jpg',
+                tags       => ['about'],
+                visibility => 'public',
+                acls       => ['admin'],
+                local_href => "/users/$query->{username}",
+                callback   => "Trog::Routes::HTML::users",
+                user       => $query->{username},
+                form       => 'profile.tx',
+            });
+            # Ensure we stop registering new users
             File::Touch::touch("config/has_users");
+
+            #hup the parent to refresh the routing table
+            my $parent = getppid;
+            kill 'HUP', $parent;
         }
 
         $query->{failed} = 1;
@@ -422,6 +443,55 @@ sub login ($query, $render_cb) {
     }, @headers);
 }
 
+sub _setup_initial_db ($dat, $user) {
+   $dat->add(
+        {
+            "aclname"    => "series",
+            "acls"       => [],
+            "callback"   => "Trog::Routes::HTML::series",
+            "data"       => "Series",
+            "href"       => "/series",
+            "local_href" => "/series",
+            "preview"    => "/img/sys/testpattern.jpg",
+            "tags"       => [qw{series topbar}],
+            visibility   => 'public',
+            "title"      => "Series",
+            user         => $user,
+            form         => 'series.tx',
+            child_form   => 'series.tx',
+        },
+        {
+            "aclname"    => "about",
+            "acls"       => [],
+            "callback"   => "Trog::Routes::HTML::series",
+            "data"       => "About",
+            "href"       => "/about",
+            "local_href" => "/about",
+            "preview"    => "/img/sys/testpattern.jpg",
+            "tags"       => [qw{series topbar public}],
+            visibility   => 'public',
+            "title"      => "About",
+            user         => $user,
+            form         => 'series.tx',
+            child_form   => 'profile.tx',
+        },
+        {
+            "aclname"      => "admin",
+            acls           => [],
+            "callback"     => "Trog::Routes::HTML::config",
+            "content_type" => "text/plain",
+            "data"         => "Config",
+            "href"         => "/config",
+            "local_href"   => "/config",
+            "preview"      => "/img/sys/testpattern.jpg",
+            "tags"         => [qw{admin}],
+            visibility     => 'private',
+            "title"        => "Configure tCMS",
+            user           => $user,
+        },
+    );
+}
+
 =head2 logout
 
 Deletes your users' session and opens the login page.
@@ -626,11 +696,13 @@ Displays identified series, not all series.
 
 sub series ($query, $render_cb) {
     my $is_admin = grep { $_ eq 'admin' } @{$query->{acls}};
-    $query->{exclude_tags} = ['topbar'] unless $is_admin;
 
-    #we are either viewed one of two ways, /series/$id or /$aclname
-    my (undef,$aclname) = split(/\//,$query->{route});
-    $query->{aclname} = $aclname if $aclname;
+    #we are either viewed one of two ways, /post/$id or /$aclname
+    my (undef,$aclname,$id) = split(/\//,$query->{route});
+    $query->{aclname} = $aclname if !$id;
+
+    # Don't show topbar series on the series page.  That said, don't exclude it from direct series view.
+    $query->{exclude_tags} = ['topbar'] if !$is_admin && $aclname && $aclname eq 'series';
 
     #XXX I'd prefer to overload id to actually *be* the aclname...
     # but this way, accomodates things like the flat file time-indexing hack.
@@ -639,7 +711,8 @@ sub series ($query, $render_cb) {
     # That will essentially necessitate it *becoming* the ID for real.
 
     #Grab the relevant tag (aclname), then pass that to posts
-    my @posts = _post_helper($query, [], $query->{acls});
+    my @posts = _post_helper($query, ['series'], $query->{acls});
+
     delete $query->{id};
     delete $query->{aclname};
 
@@ -689,7 +762,7 @@ sub users ($query, $render_cb) {
     $query->{exclude_tags} = ['about'];
 
     my @posts = _post_helper({ limit => 10000 }, ['about'], $query->{acls});
-    my @user = grep { $_->{user} eq $query->{username} } @posts;
+    my @user = grep { $_->{title} eq $query->{username} } @posts;
     $query->{id} = $user[0]->{id};
     $query->{title} = $user[0]->{title};
     $query->{user_obj} = $user[0];
@@ -796,7 +869,6 @@ sub posts ($query, $render_cb, $direct=0) {
 
     #Correct page headers
     my $ph = $themed ? _themed_title($query->{route}) : $query->{route};
-    $ph = $query->{title} if $query->{title};
 
     # Build page title if it wasn't set by a wrapping sub
     $query->{title} = "$query->{domain} : $query->{title}" if $query->{title} && $query->{domain};
@@ -813,10 +885,6 @@ sub posts ($query, $render_cb, $direct=0) {
         undef $footer;
     }
 
-    my %routemap;
-    @routemap{qw{/news /about /series /image /video /audio /files}} = qw{microblog profile series file file file file};
-    my $edittype =  $routemap{$query->{route}} // 'blog';
-
     my $older = !@posts ? 0 : $posts[-1]->{created};
     $query->{failure} //= -1;
     $query->{id} //= '';
@@ -844,12 +912,17 @@ sub posts ($query, $render_cb, $direct=0) {
         $_
     } _post_helper({}, ['series'], $query->{acls});
 
+    #TODO make this dynamic with a template subdirectory for users to plop things in
+    my $forms = [qw{microblog.tx blog.tx file.tx}];
+    my $edittype = $query->{primary_post} ? $query->{primary_post}->{child_form} : $query->{form};
+
     my $content = $processor->render('posts.tx', {
         app       => $app,
         acls      => \@acls,
         can_edit  => $is_admin,
         edittype  => $edittype,
-        post      => { tags => $tags },
+        forms     => $forms,
+        post      => { tags => $tags, form => $edittype },
         post_visibilities => \@visibuddies,
         failure   => $query->{failure},
         to        => $query->{to},

+ 11 - 0
www/styles/screen.css

@@ -412,3 +412,14 @@ p.posteditortitle {
     margin: 2rem;
     padding: 1rem;
 }
+/* Name badge on posts */
+.nameBadge {
+    background-color: black;
+    opacity: 80%;
+    color: white;
+    border-radius: 1rem;
+    text-align:center;
+    padding-left: 1rem;
+    padding-right: 1rem;
+    padding-bottom: .5rem;
+}

+ 1 - 0
www/templates/form_common.tx

@@ -5,4 +5,5 @@ Content <button class="coolbutton emojiPicker">😎</button>:
 : if ( $post.id ) {
 <input type="hidden" name="id" value="<: $post.id :>" />
 : }
+<input type="hidden" name="form" value="<: $post.form :>"></input>
 <input class="coolbutton" type="submit" value="Publish" text="Publish" />

+ 1 - 1
www/templates/notconfigured.tx

@@ -16,6 +16,6 @@
         </a>
         for full instructions on how configure tCMS.
         <br /><br />
-        Please <a href="/config" alt="Login">Log In</a> and Configure tCMS.
+        Please <a href="/login" alt="Login">Log In</a> and Configure tCMS.
     </p>
 </section>

+ 7 - 3
www/templates/posts.tx

@@ -4,10 +4,12 @@
         : if ($to) {
         : include "jsalert.tx";
         : }
+        : if ($edittype) {
         <a style="cursor:pointer" onclick="switchMenu('submissions')">[Add Post]</a><hr />
         <div id="submissions" style="display:none">
-          : include $edittype ~ ".tx";
+          : include $edittype;
         </div>
+        : }
         <script type="text/javascript" src="/scripts/fgEmojiPicker.js"></script>
         <script type="text/javascript">
         new FgEmojiPicker({
@@ -44,7 +46,7 @@
     : if ( $post.aclname && $in_series ) {
     :     next;
     : }
-    : if ( $post.is_profile && $post.user == 'Nobody' ) {
+    : if ( !$post.form ) {
     :     next;
     : }
     :if ( $tiled ) {
@@ -128,6 +130,8 @@
             : if( $post.data ) {
                 : if ($post.is_video || $post.is_image) {
                 <div id="postData" class="responsive-text">
+                : } elsif ($post.is_profile) {
+                <div id="postData" class="nameBadge">
                 : } else {
                 <div id="postData">
                 : }
@@ -146,7 +150,7 @@
             <br />
             <a style="display: inline-block;cursor:pointer;" onclick="switchMenu('<: $post.id :>-<: $post.version :>');">[Edit]</a>
             <div id="<: $post.id :>-<: $post.version :>" style="display:none;">
-                : include $post.type ~ ".tx" { post => $post };
+                : include $post.form { post => $post };
                 <form class="Submissions" action="/post/delete" method="POST" class="inline">
                     <input type="hidden" name="id" value="<: $post.id :>"></input>
                     <input type="hidden" name="to" value="<: $route :>"></input>

+ 1 - 0
www/templates/profile.tx

@@ -10,6 +10,7 @@
     <input type="hidden" name="wallpaper" value="<: $post.wallpaper :>" />
     : }
     Title  <br /><input class="cooltext" type="text" name="title" value="<: $post.title :>" />
+    <input type="hidden" name="callback" value="Trog::Routes::HTML::users" />
     : include "acls.tx";
     : include "tags.tx";
     : include "form_common.tx";

+ 8 - 0
www/templates/series.tx

@@ -1,6 +1,14 @@
 <form class="Submissions" action="/post/save" method="POST" enctype="multipart/form-data">
     Title *<br /><input required class="cooltext" type="text" name="title" placeholder="Iowa Man Destroys Moon" value="<: $post.title :>" />
+    URL *<br /><input required class="cooltext" type="text" name="local_href" placeholder="/someurl" value="<: $post.local_href :>" />
     ACL name *<br /><input required class="cooltext" type="text" name="aclname" value="<: $post.aclname :>" />
+    Input Form *<br />
+    <select required class="cooltext" name="child_form">
+    : for $forms -> $form {
+        <option value="<: $form :>"><: $form :></option>
+    : }
+    </select>
+    <input type="hidden" name="callback" value="Trog::Routes::HTML::series" />
     : include "preview.tx";
     : include "acls.tx";
     : include "tags.tx";