OSDN Git Service

implement admin/ad page
authorhylom <hylom@users.sourceforge.jp>
Thu, 31 May 2018 12:14:20 +0000 (21:14 +0900)
committerhylom <hylom@users.sourceforge.jp>
Thu, 31 May 2018 12:14:20 +0000 (21:14 +0900)
src/newslash_web/css/newslash.less
src/newslash_web/css/newslash/admin/ad_codes_manager.less [new file with mode: 0644]
src/newslash_web/lib/Newslash/Model.pm
src/newslash_web/lib/Newslash/Model/ADCodes.pm [new file with mode: 0644]
src/newslash_web/lib/Newslash/Web.pm
src/newslash_web/public/js/ad-codes-editor.js [new file with mode: 0644]
src/newslash_web/t/models/ad_codes.t [new file with mode: 0644]
src/newslash_web/templates/admin/admin_bar.html.tt2
src/newslash_web/templates/admin/ads/index.html.tt2 [new file with mode: 0644]

index 5cf1ba8..eb19c3c 100644 (file)
@@ -14,4 +14,6 @@
 @import "newslash/motd.less";
 @import "newslash/search.less";
 @import "newslash/poll.less";
-@import "newslash/progress_bar.less";
\ No newline at end of file
+@import "newslash/progress_bar.less";
+
+@import "newslash/admin/ad_codes_manager.less";
\ No newline at end of file
diff --git a/src/newslash_web/css/newslash/admin/ad_codes_manager.less b/src/newslash_web/css/newslash/admin/ad_codes_manager.less
new file mode 100644 (file)
index 0000000..e6d8868
--- /dev/null
@@ -0,0 +1,19 @@
+/* styles for ad-codes manager */
+
+#forms-wrapper {
+    display: flex;
+    #ad-codes-selector {
+        flex: 0 0 auto;
+        margin: 0 @component-vertical-margin;
+        padding: 8px;
+        width: 300px;
+        select { width: 100%; }
+    }
+    #ad-codes-editor {
+        flex: 1 0 auto;
+        margin: 0 @component-vertical-margin;
+        padding: 8px;
+        textarea { width: 100%; }
+    }
+}
+
index a0256e0..f742eda 100644 (file)
@@ -1,6 +1,7 @@
 package Newslash::Model;
 use Newslash::Model::Loader;
 
+load(ad_codes => "Newslash::Model::ADCodes");
 load(boxes => "Newslash::Model::Boxes");
 load(custom_boxes => "Newslash::Model::CustomBoxes");
 load(comments => "Newslash::Model::Comments");
diff --git a/src/newslash_web/lib/Newslash/Model/ADCodes.pm b/src/newslash_web/lib/Newslash/Model/ADCodes.pm
new file mode 100644 (file)
index 0000000..1be1a6a
--- /dev/null
@@ -0,0 +1,211 @@
+package Newslash::Model::ADCodes;
+use Newslash::Model::Base -base;
+
+sub on_start_up {
+    my $self = shift;
+
+    # create tables
+    return 1 if $self->check_readonly;
+
+    my $sqls = {
+                ns_ad_codes => <<"EOSQL",
+CREATE TABLE IF NOT EXISTS ns_ad_codes (
+  ad_id        mediumint(8) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  name         varchar(191) UNIQUE NOT NULL,
+  description  varchar(255) DEFAULT "",
+  status       enum('enabled', 'disabled') DEFAULT 'enabled',
+  content      mediumtext,
+  last_update  timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) DEFAULT CHARSET=utf8mb4
+EOSQL
+               };
+
+    my $dbh = $self->connect_db;
+    for my $table_name (keys %$sqls) {
+        if (!$self->table_exists($table_name)) {
+            my $rs = $dbh->do($sqls->{$table_name}, undef);
+            if ($rs) {
+                $self->warn("create table: $table_name");
+            }
+            else {
+                $self->warn("cannot create table: $table_name");
+            }
+        }
+    }
+    $self->disconnect_db;
+
+    return 1;
+}
+
+sub select {
+    my $self = shift;
+    my $params = {@_};
+
+    my $unique_keys = { id => "ns_ad_codes.ad_id",
+                        ad_id => "ns_ad_codes.ad_id",
+                        name => "ns_ad_codes.name",
+                      };
+    my $keys = { last_update => "ns_ad_codes.last_update",
+                 status => "ns_ad_codes.status",
+               };
+    my $timestamp = "ns_ad_codes.last_update";
+
+    my ($where_clause, $where_values, $unique) = $self->build_where_clause(unique_keys => $unique_keys,
+                                                                           keys => $keys,
+                                                                           timestamp => $timestamp,
+                                                                           params => $params);
+    my ($limit_clause, $limit_values) = $self->build_limit_clause(params => $params);
+    my ($orderby_clause, $orderby_values) = $self->build_order_by_clause(keys => $keys,
+                                                                         params => $params);
+
+    my @attrs;
+    push @attrs, @$where_values, @$limit_values, @$orderby_values;
+
+    my $dbh = $self->connect_db;
+    my $sql = <<"EOSQL";
+SELECT * FROM ns_ad_codes
+    $where_clause
+    $orderby_clause
+    $limit_clause
+EOSQL
+
+    #warn($sql);
+    #warn(Dumper(@attrs));
+
+    my $sth = $dbh->prepare($sql);
+    $sth->execute(@attrs);
+    my $feeds = $sth->fetchall_arrayref({});
+
+    if (!$feeds) {
+        $self->disconnect_db();
+        return;
+    }
+    if (@$feeds == 0) {
+        $self->disconnect_db();
+        return $unique ? undef : [];
+    }
+
+    return $feeds->[0] if $unique;
+    return $feeds;
+}
+
+sub create {
+    my $self = shift;
+    my $params = {@_};
+    my $name = $params->{name};
+    return if !$name;
+
+    my $dbh = $self->connect_db;
+    my $sql = <<"EOSQL";
+INSERT INTO ns_ad_codes
+    (name, description, status, content, last_update)
+  VALUES
+    (?,    ?,           ?,      ?,           NOW()  )
+EOSQL
+
+    my $rs = $dbh->do($sql, undef,
+                      $name,
+                      $params->{description},
+                      $params->{status} || 'enabled',
+                      $params->{content}
+                     );
+    return if !$rs;
+    my $id = $dbh->last_insert_id(undef, undef, undef, undef);
+    return $id;
+}
+
+sub update {
+    my $self = shift;
+    my $params = {@_};
+    my $ad_id = $params->{ad_id} || $params->{id};
+    return if !$ad_id;
+
+    my @cols;
+    my @attrs;
+    for my $k (qw|name description status content|) {
+        if (defined $params->{$k}) {
+            push @cols, "$k = ?";
+            push @attrs, $params->{$k};
+        }
+    }
+    my $columns = join(", ", @cols);
+    my $sql = <<"EOSQL";
+UPDATE ns_ad_codes 
+  SET $columns
+  WHERE ad_id = ?
+EOSQL
+
+    push @attrs, $ad_id;
+    my $dbh = $self->connect_db;
+    my $rs = $dbh->do($sql, undef, @attrs);
+    return if !$rs;
+    return $ad_id;
+}
+
+sub delete {
+    my $self = shift;
+    my $params = {@_};
+    my $ad_id = $params->{id} || $params->{ad_id};
+    return if !$ad_id;
+
+    my $sql = <<"EOSQL";
+DELETE FROM ns_ad_codes
+  WHERE ad_id = ?
+EOSQL
+
+    my $dbh = $self->connect_db;
+    my $rs = $dbh->do($sql, undef, $ad_id);
+    return $rs;
+}
+
+sub export_all {
+    my $self = shift;
+    my $target = shift;
+
+    if (ref($target) eq "SCALAR") {
+        $$target = $self->export_json(table => "ns_ad_codes",
+                                      sort_key => "name",
+                                      exclude => [qw(ad_id
+                                                     last_update
+                                                   )],
+                                     );
+        return 1;
+    }
+    my $output = $self->export_json(file => $target,
+                                    table => "ns_ad_codes",
+                                    sort_key => "name",
+                                    exclude => [qw(ad_id
+                                                   last_update
+                                                 )]
+                                   );
+    return if !$output;
+    return 1;
+}
+
+sub import_all {
+    my $self = shift;
+    my $target = shift;
+
+    my $output;
+
+    if (ref($target) eq "SCALAR") {
+        my $rs = $self->import_json(table => "ns_ad_codes",
+                                    json => $$target,
+                                    unique_key => "name",
+                                    exclude => [qw(ad_id
+                                                   last_update
+                                                 )]
+                                   );
+        return $rs;
+    }
+    my $rs = $self->import_json(table => "ns_ad_codes",
+                                file => $target,
+                                unique_key => "name",
+                                exclude => [qw(ad_id
+                                               last_update
+                                             )],
+                               );
+    return $rs;
+}
+
+1;
index 49756d5..c9a4623 100644 (file)
@@ -290,6 +290,7 @@ sub startup {
     $admin->get('/sidebar')->to('admin-sidebar#index');
     $admin->get('/feed')->to('admin-feed#index');
     $admin->get('/blocking')->to('admin-blocking#index');
+    $admin->get('/ad')->to('admin-ads#index');
 
     $admin->get('/repository')->to('admin-repository#index');
 
diff --git a/src/newslash_web/public/js/ad-codes-editor.js b/src/newslash_web/public/js/ad-codes-editor.js
new file mode 100644 (file)
index 0000000..8f6c559
--- /dev/null
@@ -0,0 +1,90 @@
+/* sidebar_editor.js */
+const editor = {};
+var vm;
+
+editor.run = function run (params) {
+  const data = {
+    message: '',
+    boxItems: [],
+  };
+  const computed = {};
+  const methods = {
+    editItem: function editItem(item) {
+      item.editing = true;
+      item.edited = true;
+    },
+    deleteItem: function deleteItem() {
+      const target = [];
+      for (var i = 0; i < this.boxItems.length; i++) {
+        if (this.boxItems[i].selected) {
+          target.push({id: this.boxItems[i].id, index: i});
+        }
+      }          
+      if (target.length == 0) {
+        return;
+      }
+      const msg = "delete " + target.length + " item(s)?";
+      const result = window.confirm(msg);
+      if (result) {
+        // delete items
+        target.reverse();
+        target.forEach(item => {
+          newslash.admin.deleteBox(item.id).then(
+            (response) => { // success
+              if (response.error) {
+                this.message = "error: " + response.message;
+                return;
+              }
+              this.boxItems.splice(item.index, 1);
+            },
+            (response) => { // fail
+              this.message = "error";
+            }
+          );
+        });
+      }
+    },
+    newItem: function newItem() {
+      const item = {
+        id: 0,
+        name: 'new item',
+        comment: '',
+        model: '',
+        query_params: '',
+        limit: '',
+        template: '',
+        selected: false,
+        editing: true,
+        edited: true,
+      };
+      const it = this.boxItems.push(item);
+    },
+  };
+
+  function initItems (items) {
+    items.forEach(item => {
+      Vue.set(item, 'selected', false);
+      Vue.set(item, 'editing', false);
+      Vue.set(item, 'edited', false);
+    });
+  };
+
+  vm = new Vue({el: params.el,
+                data: data,
+                computed: computed,
+                methods: methods,
+                created: function () {
+                  newslash.admin.getBoxes().then(
+                    (resp) => { // success
+                      this.boxItems = resp.items || [];
+                      this.message = resp.message || '';
+                      initItems(this.boxItems);
+                    },
+                    (resp) => { // fail
+                      this.boxItems = [];
+                      this.message = resp.message || "failed to get items";
+                    }
+                  );
+                },
+               });
+};
diff --git a/src/newslash_web/t/models/ad_codes.t b/src/newslash_web/t/models/ad_codes.t
new file mode 100644 (file)
index 0000000..033d85c
--- /dev/null
@@ -0,0 +1,49 @@
+# -*-Perl-*-
+# Newslash::Model::ADCodes related  tests
+use Mojo::Base -strict;
+
+use Test::More;
+use Test::Mojo;
+use Data::Dumper;
+use DateTime::Format::MySQL;
+
+my $t = Test::Mojo->new('Newslash::Web');
+
+my $ad_codes = $t->app->model('ad_codes');
+ok($ad_codes, "get AdCodes instance");
+
+subtest 'create/select/update/delete ad_codes and items' => sub {
+    plan skip_all => "mode is not 'test'" if ($t->app->mode ne 'test');
+
+    my $test_name = "test_feed";
+    my $test_content = "テストのコード本体";
+
+    my $rs = $ad_codes->select(name => $test_name);
+    $ad_codes->delete(ad_id => $rs->{ad_id}) if $rs;
+
+    my $ad_id = $ad_codes->create(name => $test_name,
+                                  content => $test_content);
+    ok($ad_id, "create ad_code");
+
+    my $ad_code = $ad_codes->select(ad_id => $ad_id);
+    ok($ad_code, "select ad_code by ad_id");
+    is($ad_code->{name}, $test_name, "retrive valid name");
+    is($ad_code->{status}, "enabled", "retrive valid status");
+    is($ad_code->{content}, $test_content, "retrive valid content");
+
+    $ad_code = $ad_codes->select(name => $test_name);
+    ok($ad_code, "select ad_code by name");
+    is($ad_code->{name}, $test_name, "retrive valid ad_code");
+
+    my $test_description = "説明2";
+    ok($ad_codes->update(ad_id => $ad_id,
+                         description => $test_description),
+       "update ad_code");
+
+    ok($ad_codes->delete(id => $ad_id), "delete item");
+};
+
+
+# cleanup
+
+done_testing();
index 5e5cbae..b3847c0 100644 (file)
 
           <!-- CSS -->
          <li class="dropdown">
-           <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">CSS<span class="caret"></span></a>
+           <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">コンテンツ<span class="caret"></span></a>
            <ul class="dropdown-menu">
               <li><a href="/admin/css">CSS更新</a></li>
+              <li><a href="/admin/ad">広告コード</a></li>
            </ul>
          </li>
 
diff --git a/src/newslash_web/templates/admin/ads/index.html.tt2 b/src/newslash_web/templates/admin/ads/index.html.tt2
new file mode 100644 (file)
index 0000000..dd0ef15
--- /dev/null
@@ -0,0 +1,38 @@
+[% WRAPPER common/layout %]
+
+<div class="app-frame" id="ad-code-manager">
+  <h3>AD Code Manager</h3>
+  <div v-text="message">
+  </div>
+  <form>
+    <div id="forms-wrapper">
+      <div id="ad-codes-selector">
+        <select size="30"></select>
+      </div>
+      
+      <div id="ad-codes-editor">
+        <div>
+          <label>AD_CODE_ID<input /></label>
+        </div>
+        <div>
+          <label>説明<input /></label>
+        </div>
+        <div>
+          <label>広告コード:
+            <textarea></textarea>
+          </label>
+        </div>
+        <div>
+          <button class="btn btn-default" type="button">保存</button>
+        </div>
+      </div>
+    </div>
+  </form>
+</div>
+
+<script src="/js/ad-codes-editor.js" ></script>
+<script>
+  /* editor.run({ el: '#ad-code-manager' }); */
+</script>
+
+[% END %]