DUMMY.pm 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package Trog::Data::DUMMY;
  2. use strict;
  3. use warnings;
  4. no warnings 'experimental';
  5. use feature qw{signatures};
  6. use Carp qw{confess};
  7. use JSON::MaybeXS;
  8. use File::Slurper;
  9. =head1 WARNING
  10. Do not use this as a production data model. It is *not* safe to race conditions, and is only here for testing.
  11. =head1 QUERY FORMAT
  12. The $query_language and $query_help variables are presented to the user as to how to use the search box in the tCMS header.
  13. =cut
  14. our $datastore = 'data/DUMMY.json';
  15. our $query_language = 'Perl Regex in Quotemeta';
  16. our $query_help = 'https://perldoc.perl.org/functions/quotemeta.html';
  17. =head1 POST STRUCTURE
  18. Posts generally need to have the following:
  19. data: Brief description of content, or the content itself.
  20. content_type: What this content actually is. Used to filter into the appropriate pages.
  21. href: Primary link. This is the subject of a news post, or a link to the item itself. Can be local or remote.
  22. local_href: Backup link. Automatically created link to a static cache of the content.
  23. title: Title of the content. Used as link name for the 'href' attribute.
  24. user: User was banned for this post
  25. id: Internal identifier in datastore for the post.
  26. tags: array ref of appropriate tags.
  27. created: timestamp of creation of this version of the post
  28. version: revision # of this post.
  29. =cut
  30. =head1 CONSTRUCTOR
  31. =head2 new(Config::Simple $config)
  32. Try not to do expensive things here.
  33. =cut
  34. sub new ($class, $config) {
  35. $config = $config->vars();
  36. $config->{lang} = $query_language;
  37. $config->{help} = $query_help;
  38. return bless($config,__PACKAGE__);
  39. }
  40. =head1 METHODS
  41. =head2 get(%request)
  42. Queries the data model in the way a "real" data model module ought to.
  43. id => Filter down to just the post by ID. May be subsequently filtered by ACL, resulting in a 404 (which is good, as it does not disclose info).
  44. tags => ARRAYREF of tags, any one of which is required to give a result. If none are passed, no filtering is performed.
  45. acls => ARRAYREF of acl tags, any one of which is required to give result. Filter applies after tags. 'admin' ACL being present skips this filter.
  46. page => Offset multiplier for pagination.
  47. limit => Offset for pagination.
  48. like => Search query, as might be passed in the search bar.
  49. =cut
  50. sub _read {
  51. confess "Can't find datastore!" unless -f $datastore;
  52. my $slurped = File::Slurper::read_text($datastore);
  53. return JSON::MaybeXS::decode_json($slurped);
  54. }
  55. sub _write($data) {
  56. open(my $fh, '>', $datastore) or confess;
  57. print $fh JSON::MaybeXS::encode_json($data);
  58. close $fh;
  59. }
  60. # These have to be sorted as requested by the client
  61. sub get ($self, %request) {
  62. my $example_posts = _read();
  63. my @filtered = @$example_posts;
  64. # If an ID is passed, just get that
  65. @filtered = grep { $_->{id} eq $request{id} } @filtered if $request{id};
  66. # Next, handle the query, tags and ACLs
  67. @filtered = grep { my $tags = $_->{tags}; grep { my $t = $_; grep {$t eq $_ } @{$request{tags}} } @$tags } @filtered if @{$request{tags}};
  68. @filtered = grep { my $tags = $_->{tags}; grep { my $t = $_; grep {$t eq $_ } @{$request{acls}} } @$tags } @filtered unless grep { $_ eq 'admin' } @{$request{acls}};
  69. @filtered = grep { $_->{data} =~ m/\Q$request{like}\E/i } @filtered if $request{like};
  70. # Finally, paginate
  71. my $offset = int($request{limit});
  72. $offset = @filtered < $offset ? @filtered : $offset;
  73. my $pages = int(scalar(@filtered) / ($offset || 1) );
  74. @filtered = splice(@filtered, ( int($request{page}) -1) * $offset, $offset) if $request{page} && $request{limit};
  75. # Next, go ahead and build the "post type"
  76. @filtered = _add_post_type(@filtered);
  77. # Next, add the type of post this is
  78. @filtered = _add_media_type(@filtered);
  79. # Finally, add visibility
  80. @filtered = _add_visibility(@filtered);
  81. return ($pages,\@filtered);
  82. }
  83. sub total_posts {
  84. my $example_posts = _read();
  85. return scalar(@$example_posts);
  86. }
  87. sub _add_post_type (@posts) {
  88. return map {
  89. my $post = $_;
  90. my $type = 'file';
  91. $type = 'blog' if grep { $_ eq 'blog' } @{$post->{tags}};
  92. $type = 'microblog' if grep { $_ eq 'news' } @{$post->{tags}};
  93. $type = 'profile' if grep { $_ eq 'profile' } @{$post->{tags}};
  94. $type = 'series' if grep { $_ eq 'series' } @{$post->{tags}};
  95. $post->{type} = $type;
  96. $post
  97. } @posts;
  98. }
  99. sub _add_media_type (@posts) {
  100. return map {
  101. my $post = $_;
  102. $post->{is_video} = 1 if $post->{content_type} =~ m/^video\//;
  103. $post->{is_audio} = 1 if $post->{content_type} =~ m/^audio\//;
  104. $post->{is_image} = 1 if $post->{content_type} =~ m/^image\//;
  105. $post
  106. } @posts;
  107. }
  108. sub _add_visibility (@posts) {
  109. return map {
  110. my $post = $_;
  111. my @visibilities = grep { my $tag = $_; grep { $_ eq $tag } qw{private unlisted public} } @{$post->{tags}};
  112. $post->{visibility} = $visibilities[0];
  113. $post
  114. } @posts;
  115. }
  116. sub add ($self, @posts) {
  117. require UUID::Tiny;
  118. my $example_posts = _read();
  119. foreach my $post (@posts) {
  120. $post->{id} //= UUID::Tiny::create_uuid_as_string(UUID::Tiny::UUID_V1, UUID::Tiny::UUID_NS_DNS);
  121. my (undef, $existing_posts) = $self->get( id => $post->{id} );
  122. if (@$existing_posts) {
  123. my $existing_post = $existing_posts->[0];
  124. $post->{version} = $existing_post->{version};
  125. $post->{version}++;
  126. }
  127. push @$example_posts, $post;
  128. }
  129. _write($example_posts);
  130. return 0;
  131. }
  132. sub delete($self, @posts) {
  133. my $example_posts = _read();
  134. foreach my $update (@posts) {
  135. @$example_posts = grep { $_->{id} ne $update->{id} } @$example_posts;
  136. }
  137. _write($example_posts);
  138. return 0;
  139. }
  140. 1;