現状の状態をcommit. feature/book_server_client_add_restapi
authorOHASHI, Norikazu <katz@neko-mori.sakura.ne.jp>
Mon, 18 Sep 2023 04:56:20 +0000 (13:56 +0900)
committerOHASHI, Norikazu <katz@neko-mori.sakura.ne.jp>
Mon, 18 Sep 2023 04:56:20 +0000 (13:56 +0900)
13 files changed:
.gitignore
.ruby-version [new file with mode: 0644]
.yardopts
client/src/.tern-port
restapi.apib
sinatra/Gemfile
sinatra/app/controllers/restful_api.rb
sinatra/app/controllers/spa_gui.rb
sinatra/app/controllers/web_gui.rb
sinatra/app/models/books_db.rb
sinatra/app/models/tokens_db.rb
sinatra/app/models/users_db.rb
sinatra/app/views/layout.haml

index 13b4438..a692666 100644 (file)
@@ -6,6 +6,8 @@
 /sinatra/Gemfile.lock
 /sinatra/vendor/
 /sinatra/public/scripts/
+/sinatre/public/css/
 /client/node_modules/
 *.log
 *.org
+restapi.html
diff --git a/.ruby-version b/.ruby-version
new file mode 100644 (file)
index 0000000..6a81b4c
--- /dev/null
@@ -0,0 +1 @@
+2.7.8
index 9fedcb7..764c0a9 100644 (file)
--- a/.yardopts
+++ b/.yardopts
@@ -1,7 +1,12 @@
 --plugin yard-sinatra
---type-name-tag post_param:"POST Parameters"
 --type-name-tag path_param:"Path Paramaters"
+--type-name-tag query_param:"Query Parameters"
+--type-name-tag post_param:"POST Parameters"
+--type-name-tag request_param:"Parameters with request body"
+--type-name-tag response_param:"Parameters with response body"
 --markup-provider=redcarpet
 --markup=markdown
+sinatra/app/controllers/*.rb
+sinatra/app/models/*.rb
 -
 readme.md
index b6c93a1..01c29ac 100644 (file)
@@ -1 +1 @@
-57153
\ No newline at end of file
+49939
\ No newline at end of file
index 5ba1649..580ba02 100644 (file)
@@ -117,6 +117,14 @@ HOST: http://www.book-server2.neko-mori.org/webapi/v1
 対象のユーザ情報が存在しない。
     + Attributes (Error)
 
++ Response 403 (application/json)
+操作権限がない。
+    + Attributes (Error)
+
++ Response 401 (application/json)
+認証失敗
+    + Attributes (Error)
+
 #### ユーザ削除 [DELETE]
 ユーザ情報の削除を行う (要管理者権限)
 
@@ -172,16 +180,16 @@ HOST: http://www.book-server2.neko-mori.org/webapi/v1
     + Attributes (Error)
 
 + Response 406 (application/json)
-すでに同一ISDNが蔵書一覧に登録済み
+すでに同一ISBNが蔵書一覧に登録済み
     + Attributes (Error)
         
-### 蔵書個別 [/book_collections/{isdn}{?token}]
+### 蔵書個別 [/book_collections/{isbn}{?token}]
 + Parameter
-    + isdn: `978412345678` (string, required) - 対象書籍のISDN
+    + isbn: `978412345678` (string, required) - 対象書籍のISBN
     + token: `5dd5eopo3kpXO19YWVgP8A` (string, required) - APIアクセス用のトークン
 
 #### 蔵書詳細 [GET]
-指定したISDNの蔵書情報を取得
+指定したISBNの蔵書情報を取得
 
 + Response 200 (application/json)
 正常終了
@@ -196,10 +204,10 @@ HOST: http://www.book-server2.neko-mori.org/webapi/v1
     + Attributes (Error)
    
 #### 蔵書更新 [PUT]
-指定したISDNの蔵書情報の更新
+指定したISBNの蔵書情報の更新
 
 + Request (application/Json)
-    + Attributes (BookCollectionRegistData)
+    + Attributes (BookCollectionUpdateData)
     
 + Response 200 (application/json)
 正常終了
@@ -215,7 +223,7 @@ HOST: http://www.book-server2.neko-mori.org/webapi/v1
 
 
 #### 蔵書削除 [DELETE]
-指定したISDNの蔵書情報の蔵書一覧からの削除
+指定したISBNの蔵書情報の蔵書一覧からの削除
 
 + Response 200 (application/json)
 正常終了
@@ -247,13 +255,13 @@ HOST: http://www.book-server2.neko-mori.org/webapi/v1
 セッション切れ
     + Attributes (Error)
 
-### 書籍個別 [/books/{isdn}{?token}]
+### 書籍個別 [/books/{isbn}{?token}]
 + Parameters
-    + isdn: `978412345678` (string, required) - 対象書籍のISDN
+    + isbn: `978412345678` (string, required) - 対象書籍のISBN
     + token: `5dd5eopo3kpXO19YWVgP8A` (string, required) - APIアクセス用のトークン
 
 #### 書籍詳細取得 [GET]
-ISDNで指定された書籍の詳細情報を取得する。システムに未登録の場合 Internet 上から取得する。
+ISBNで指定された書籍の詳細情報を取得する。システムに未登録の場合 Internet 上から取得する。
 
 + Response 200 (application/json)
 正常終了
@@ -268,7 +276,7 @@ ISDNで指定された書籍の詳細情報を取得する。システムに未
     + Attributes (Error)
 
 #### 書籍情報の削除 [DELETE]
-ISDNで指定された書籍の全情報を削除する。 (管理者権限でのみ実施可能)
+ISBNで指定された書籍の全情報を削除する。 (管理者権限でのみ実施可能)
 
 + Response 200 (application/json)
 正常終了
@@ -302,7 +310,10 @@ ISDNで指定された書籍の全情報を削除する。 (管理者権限で
 + user_role: `8` (number, required) - ユーザの権限 [0: 管理者権限, 8: 一般権限, 10: 無効]
 
 ## Book
-+ isdn: `978412345678` (string, required) - 対象書籍のISDN
++ isbn: `978412345678` (string, required) - 対象書籍のISBN
++ Include UpdateBook
+
+## UpdateBook
 + title: `羅生門` (string, required) - 書籍のタイトル
 + volume: `0` (number, optional) - 書籍の巻数
 + subtitle: `人間の業` (string, optional) - 書籍のサブタイトル
@@ -322,8 +333,14 @@ ISDNで指定された書籍の全情報を削除する。 (管理者権限で
 + summary: `羅城門は芥川の書いた作品の...` (string, optional) - 書籍の説明
 + book_rank: `5` (number,optional) - 書籍の評価値
 
+## BookCollectionUpdateData
++ Include UpdateBook
++ cover_data: `data:image/png;base64,q/9j/4AAQSkZJRgABAQAAAQABAAD...` (string, optional) - カバー画のBAS64encode
++ summary: `羅城門は芥川の書いた作品の...` (string, optional) - 書籍の説明
++ book_rank: `5` (number,optional) - 書籍の評価値
+
 ## BookListItem
-+ isdn: `978412345678` (string, required) - 対象書籍の
++ isbn: `978412345678` (string, required) - 対象書籍のISBN
 + title: `羅生門` (string, required) - 書籍のタイトル
 + volume: `0` (number, optional) - 書籍の巻数
 + author: `芥川龍之介` (string, optional) - 著者名
@@ -339,3 +356,5 @@ ISDNで指定された書籍の全情報を削除する。 (管理者権限で
 
 ## Error
 + code: `E0000000` (string,required) - エラーコード
++ message: `Error message.` (string,required) - エラーメッセージ
+
index 37f4e1b..88fa6d2 100644 (file)
@@ -20,4 +20,5 @@ gem 'sass'
 gem 'scss'
 gem 'coffee-script'
 gem 'unicorn'
+gem 'data_uri'
 # gem 'mini_racer'
index b76cae0..7a11998 100644 (file)
@@ -11,7 +11,7 @@ require 'sinatra/json'
 require 'logger'
 require 'json'
 require 'securerandom'
-
+require 'data_uri'
 
 # ユーザアクセス用モデル
 require_relative '../models/users_db'
@@ -48,32 +48,90 @@ class RESTfulAPI < Sinatra::Base
     @tokens = TokenManager.instance
   end
   
+  helpers do
+    
+    # 更新用の書籍情報を作成する。
+    # @param [Hash] params Postで取得した全パラメータ
+    # @param [Boolean] image_f 書影データの有無
+    # @return [Hash] 更新用書籍情報
+    def makeBookInfo(params)
+      book_info = {}
+      params.each do |key, value|
+        case key
+        when 'summary', 'book_rank', 'cover_data' then
+          # 対象キーは書籍情報ではないので飛す
+          next
+        end
+        book_info[key.to_sym] = value
+      end
+      return book_info
+    end
+
+    # 書影情報の作成
+    # @param [String] cover_data 書影データのData URI scheme(bas64)
+    # @return [Hash] 作成した書影データ
+    def setBookImage(cover_data)
+      cover_image = {}
+      # 書影情報の取得
+      data_uri = URI::Data.new(cover_data)
+      cover_image[:mime_type] = data_uri.content_type
+      cover_image[:data] =  data_uri.data
+      return cover_image
+    end
+  end
+
+  
+
   # ユーザログイン
+  # @path_param [String] version API Version
+  # @example Request body 
+  #   {
+  #     "user_name": "user01",
+  #     "password": "password"
+  #   }
+
   post '/:version/login' do
     begin
+      # パラメータ取得
       body = JSON.parse(request.body.read)
       name = body['user_name']
       passwd = body['password']
+
+      # ログイン情報確認
       id = @user_account.checkPasswd(name, passwd)
+      # トークン取得
       token = @tokens.getToken(id)
       logger.info("Get session of book server. id: #{id}, user_name: #{name}, token #{token}")
+
+      # レスポンスボディー作成
       response = {:token => token, :id => id}
       json response
+      
     rescue UserAccount::NotFoundInstanceError,
            UserAccount::AuthenticationError
-      logger.error("Invaild user or password. user_name: #{name}")
-      raise WebError.new(:status => 401, :code => 'E01001')
+      
+      # 認証失敗
+      raise WebError.new(:status => 401, :code => 'E01001',
+                         :message => "Invaild user or password. user_name: #{name}")
+      
     rescue JSON::ParserError => e
-      logger.error("Failed to parse JSON. #{e.message}")
-      raise WebError.new(:status => 400, :code => 'E01002')
+      
+      # JSONパースエラー
+      raise WebError.new(:status => 400, :code => 'E01002',
+                         :message => "Failed to parse JSON. #{e.message}")
     end
   end
 
   # ユーザーログアウト
+  # @path_param [String] version API Version
+  # @query_param [String] token APIアクセス用トークン
+  
   delete '/:version/logout' do
     begin
+      # トークからユーザーIDの取得
       token = params[:token]
       @tokens.releseToken(token)
+      
       logger.info("Logout session of book server. token: #{token}")
       response = {}
       json response
@@ -81,17 +139,33 @@ class RESTfulAPI < Sinatra::Base
   end
 
   # ユーザ一覧取得
+  # @path_param [String] version API Version
+  # @query_param [String] token APIアクセス用トークン
+  # @example Response Body
+  #   [
+  #     {
+  #       "id": 15,
+  #       "user_name": "user01",
+  #       "full_name": "Taro Suzuki",
+  #       "email": "user01@book-server2.neko-mori.org",
+  #       "user_role": 8
+  #     }
+  #   ]
   get '/:version/users' do
     begin
+      # トークンからユーザーIDの取得
       token = params[:token]
       id = @token.getId(token)
       
       # 管理者権限でなければ利用できない
-      raise WebError.new(:status => 403, :code => "E02001") unless
-        @user_account.checkRole(id, [User::ROLE_ADMIN])
+      raise WebError.new(:status => 403, :code => "E02001",
+                         :message => "User is without authority. userid=#{id}" 
+                        ) unless @user_account.checkRole(id, [User::ROLE_ADMIN])
 
+      # ユーザ一覧の取得
       users = @user_account.getUserList
 
+      # レスポンスボディーの生成
       response = users.map {|u|
         { :id => u.user_id, :user_name => u.user_name,
           :full_name => u.full_name, :email => u.emaiil,
@@ -101,53 +175,96 @@ class RESTfulAPI < Sinatra::Base
 
     rescue TokenManager::UnknownTokenError,
            TokenManager::ExpiredTokenError
-      logger.error("Session expired of #{token}")
-      raise WebError.new(:status => 408, :code => "E02002")
+
+      # トークン認証失敗
+      raise WebError.new(:status => 408, :code => "E02002",
+                         :message => "Session expired of #{token}")
     end
   end
 
   #ユーザ登録
-  put '/:version/users' do
+  # @path_param [String] version API Version
+  # @query_param [String] tonken APIアクセス用トークン
+  # @example Request Body
+  #   {
+  #     "user_name": "user01",
+  #     "full_name": "Taro Suzuki",
+  #     "email": "user01@book-server2.neko-mori.org",
+  #     "user_role": 8,
+  #     "password": "password00"
+  #   }  
+  post '/:version/users' do
     begin
+      # トークンからユーザーIDの取得
       token = params[:token]
       id = @token.getId(token)
       
       # 管理者権限でなければ利用できない
-      raise WebError.new(:status => 403, :code => "E03001") unless
-        @user_account.checkRole(id, [User::ROLE_ADMIN, User::ROLE_APPUSE])
-      
+      raise WebError.new(:status => 403, :code => "E03001",
+                         :message => "User is without authority. userid=#{id}"
+                        ) unless @user_account.checkRole(id, [User::ROLE_ADMIN, User::ROLE_APPUSE])
+
+      # リクエストボディのJSONをパースする。
       body = JSON.parse(request.body.read)
 
-     create_id = createAccount(body['user_name'], body['full_name'],
+      # ユーザ作成
+      create_id = createAccount(body['user_name'], body['full_name'],
                    body['emaiil'], body['password'], body['user_role']);
-      
+
+      # レスポンスボディーを生成
       response={:id => create_id}
 
       json response
       
     rescue TokenManager::UnknownTokenError,
            TokenManager::ExpiredTokenError
-      logger.error("Session expired of #{token}")
-      raise WebError.new(:status => 408, :code => "E03002")
+
+      # トークン認証失敗
+      raise WebError.new(:status => 408, :code => "E03002",
+                         :message => "Session expired of #{token}")
+      
     rescue UserAccount::AlreadyInstanceError
-      logger.error("Already exist user_name  #{body['user_name']}")
-      raise WebError.new(:status => 408, :code => "E03003")
+
+      # すでに同名のユーザーが登録済み
+      logger.error()
+      raise WebError.new(:status => 408, :code => "E03003",
+                         :message => "Already exist user_name  #{body['user_name']}")
+      
     rescue JSON::ParserError => e
-      logger.error("Failed to parse JSON. #{e.message}")
-      raise WebError.new(:status => 400, :code => 'E03004')
+
+      # JSONパースエラー
+      logger.error()
+      raise WebError.new(:status => 400, :code => 'E03004',
+                         :message => "Failed to parse JSON. #{e.message}")
     end
   end
 
   # ユーザ詳細
+  # @path_param [String] version API Version
+  # @path_param [String] id 取得対象のユーザーID
+  # @query_param [String] token APIアクセス用トークン
+  # @example Response Body
+  #   {
+  #     "id": 15,
+  #     "user_name": "user01",
+  #     "full_name": "Taro Suzuki",
+  #     "email": "user01@book-server2.neko-mori.org",
+  #     "user_role": 8
+  #   }  
   get '/:version/users/:id' do
     begin
+      #パラメータ取得
       token = params[:token]
       read_id = params[:id]
     
+      # トークンからユーザーIDの取得
       id = @token.getId(token)
-      raise WebError.new(:status => 403, :code => "E04001") if
-        !@user_account.checkRole(id, [User::ROLE_ADMIN]) && id != read_id
-      
+      # 管理者権限かログインしているユーザでなければ利用できない
+      raise WebError.new(:status => 403, :code => "E04001",
+                         :message => "User is without authority. userid=#{id}"
+                        ) if !@user_account.checkRole(id, [User::ROLE_ADMIN]) && id != read_id
+
+      # ユーザ情報を取得してレソポンスボディー用の情報に変換
       user = @user_account.getUser(id)
       response = { :id => user.user_id, :user_name => user.user_name,
                    :full_name => user.full_name, :email => user.emaiil,
@@ -156,29 +273,51 @@ class RESTfulAPI < Sinatra::Base
       
     rescue TokenManager::UnknownTokenError,
            TokenManager::ExpiredTokenError
-      logger.error("Session expired of #{token}")
-      raise WebError.new(:status => 408, :code => "E04002")
+
+      # トークン認証失敗
+      raise WebError.new(:status => 408, :code => "E04002",
+                         :message => "Session expired of #{token}")
+      
     rescue UserAccount::NotFoundInstanceError
-      logger.error("Not found user. user id: #{update_id}")
-      raise WebError.new(:status => 404, :code => "E04003")
+
+      # 対象ユーザーの情報がない
+      raise WebError.new(:status => 404, :code => "E04003",
+                         :message => "Not found user. user id: #{update_id}")
     end
   end
   
-  # ユーザ詳細
+  # ユーザ更新
+  # @path_param [String] version API Version
+  # @path_param [String] id 取得対象のユーザーID
+  # @query_param [String] tonken APIアクセス用トークン
+  # @example Request Body
+  #   {
+  #     "user_name": "user01",
+  #     "full_name": "Taro Suzuki",
+  #     "email": "user01@book-server2.neko-mori.org",
+  #     "user_role": 8,
+  #     "password": "password00",
+  #     "orignal_password": "oldPassword"
+  #   }
   put '/:version/users/:id' do
     begin
+      #パラメータ取得
       token = params[:token]
       update_id = params[:id]
     
+      # トークンからユーザーIDの取得
       id = @token.getId(token)
-      raise WebError.new(:status => 403, :code => "E05001") if
-        !@user_account.checkRole(id, [User::ROLE_ADMIN]) && id != update_id
-
+      # 管理者権限かログインしているユーザでなければ利用できない
+      raise WebError.new(:status => 403, :code => "E05001",
+                         :message => "User is without authority. userid=#{id}"
+                        ) if !@user_account.checkRole(id, [User::ROLE_ADMIN]) && id != update_id
       
       body = JSON.parse(request.body.read)
 
-      raise WebError.new(:status => 401, :code => "E05002") if
-        id == update_id && !checkPasswd!(id, body['orignal_password'])
+      # パスワード更新で元パスワードが異なる
+      raise WebError.new(:status => 401, :code => "E05002",
+                         :message => "Authentication failed. userid=#{id}") if
+        !@user_account.checkPasswdOfId(id, body['orignal_password'])
 
       params = { :passwd => body['password'], :user_name => body['user_name'],
                  :full_name => body['full_name'], :email => body['email'] }
@@ -189,22 +328,38 @@ class RESTfulAPI < Sinatra::Base
 
     rescue TokenManager::UnknownTokenError,
            TokenManager::ExpiredTokenError
-      logger.error("Session expired of #{token}")
-      raise WebError.new(:status => 408, :code => "E05003")
+
+      # トークン認証失敗
+      raise WebError.new(:status => 408, :code => "E05003",
+                         :message => "Session expired of #{token}")
+      
     rescue UserAccount::NotFoundInstanceError
-      logger.error("Not found user. user id: #{update_id}")
-      raise WebError.new(:status => 404, :code => "E05004")
+
+      # 対象ユーザーの情報がない
+      raise WebError.new(:status => 404, :code => "E05004", :message => "Not found user. user id: #{update_id}")
+      
+    rescue JSON::ParserError => e
+
+      # JSONパースエラー
+      raise WebError.new(:status => 400, :code => 'E05005',
+                         :message => "Failed to parse JSON. #{e.message}")
     end
   end
 
+  # ユーザ削除
+  # @path_param [String] version API Version
+  # @path_param [String] id 取得対象のユーザーID
+  # @query_param [String] tonken APIアクセス用トークン
   delete '/:version/users/:id' do
     begin
       token = params[:token]
       delete_id = params[:id]
     
       id = @token.getId(token)
-      raise WebError.new(:status => 403, :code => "E06001") unless
-        @user_account.checkRole(id, [User::ROLE_ADMIN])
+      # 管理者権限でなければ利用できない
+      raise WebError.new(:status => 403, :code => "E06001",
+                         :message => "User is  without authority. userid=#{id}"
+                        ) unless @user_account.checkRole(id, [User::ROLE_ADMIN])
 
       @user_account.deleteUser(delete_id)
 
@@ -213,27 +368,46 @@ class RESTfulAPI < Sinatra::Base
 
     rescue TokenManager::UnknownTokenError,
            TokenManager::ExpiredTokenError
-      logger.error("Session expired of #{token}")
-      raise WebError.new(:status => 408, :code => "E06002")
+
+      # トークン認証失敗
+      raise WebError.new(:status => 408, :code => "E06002",
+                         :message => "Session expired of #{token}")
+      
     rescue UserAccount::NotFoundInstanceError
-      logger.error("Not found user. user id: #{update_id}")
-      raise WebError.new(:status => 404, :code => "E06003")
+      
+      # 対象ユーザーの情報がない
+      raise WebError.new(:status => 404, :code => "E06003",
+                         :message => "Not found user. user id: #{update_id}")
     end
   end
 
+  # ユーザ所持の蔵書一覧取得
+  # @path_param [String] version API Version
+  # @query_param [String] tonken APIアクセス用トークン
+  # @query_param [String] search 検索条件
+  # @example Response Body
+  #   [
+  #     {
+  #       "isbn": "978412345678",
+  #       "title": "羅生門",
+  #       "volume": 0,
+  #       "author": "芥川龍之介",
+  #       "orignal_author": "芥川龍之介",
+  #       "illustrator": "奥山誠司",
+  #       "publisher": "角川文庫",
+  #       "pubdate": "2012/01/20"
+  #     }
+  #   ]
   get '/:version/book_collections' do
     begin
       token = params[:token]
-      search = params[:search]
-
+      search = JSON.perse(params[:search])
       id = @token.getId(token)
-      raise WebError.new(:status => 403, :code => "E06001") unless
-        @user_account.checkRole(id, [User::ROLE_ADMIN])
-      book_list, full_size = @book_manager.narrowBooOfId(id,0,0, search)
+      book_list, full_size = @book_manager.narrowBookOfId(id,0,0, search)
       if full_size > 0 
         response = book_list.map { |book|
           {
-            :isdn => book.isdn, :title => book.title, :volume => book.volume,
+            :isbn => book.isbn, :title => book.title, :volume => book.volume,
             :author => book.author, :original_author => book.original_author,
             :illustrator => book.illustrator, :publisher => book.publisher,
             :pubdate => book.pubdate,
@@ -243,14 +417,345 @@ class RESTfulAPI < Sinatra::Base
         response = []
       end
       json response
+      
+    rescue TokenManager::UnknownTokenError,
+           TokenManager::ExpiredTokenError
+      
+      # トークン認証失敗
+      raise WebError.new(:status => 408, :code => "E07002",
+                         :message => "Session expired of #{token}")
+      
+    rescue JSON::ParserError => e
+      
+      # JSONパースエラー
+      raise WebError.new(:status => 400, :code => 'E07003',
+                         :message => "Failed to parse JSON. #{e.message}")
+    end
+  end
+
+  # ユーザ所持の書籍の登録
+  # @path_param [String] version API Version
+  # @query_param [String] tonken APIアクセス用トークン
+  # @example Request Body
+  #   {
+  #     "isbn": "978412345678",
+  #     "title": "羅生門",
+  #     "volume": 0,
+  #     "subtitle": "人間の業",
+  #     "series": "芥川全集",
+  #     "author": "芥川龍之介",
+  #     "orignal_author": "芥川龍之介",
+  #     "illustrator": "奥山誠司",
+  #     "translator": "ジェイ・ルービン",
+  #     "supervisor": "角川春樹",
+  #     "publisher": "角川文庫",
+  #     "pubdate": "2012/01/20",
+  #     "cover_uri": "https://cover.openbd.jp/9784107723369.jpg",
+  #     "cover_data": "data:image/png;base64,q/9j/4AAQSkZJRgABAQAAAQABAAD...",
+  #     "summary": "羅城門は芥川の書いた作品の...",
+  #     "book_rank": 5
+  #   }  
+  post '/:version/book_collections' do
+    begin
+      token = params[:token]
+      id = @token.getId(token)
+      book = JSON.parse(request.body.read)
+
+      @book_manager.createCollectBook(id, book)
+      response = {}
+      json response
+      
+    rescue TokenManager::UnknownTokenError,
+           TokenManager::ExpiredTokenError
+
+      raise WebError.new(:status => 408, :code => "E08001",
+                         :message => "Session expired of #{token}")
+
+    rescue BookManager::AlreadyInstanceError
+
+      raise WebError.new(:status => 406, :code => "E08002",
+                         :message => "Already exsit target book. isbn=#{isbn}")
+    end
+  end
+
+  # ユーザ所持の蔵書詳細取得
+  # @path_param [String] version API Version
+  # @path_param [String] isbn 対象書籍のISBN
+  # @query_param [String] tonken APIアクセス用トークン
+  # @example Response Body
+  #   {
+  #     "isbn": "978412345678",
+  #     "title": "羅生門",
+  #     "volume": 0,
+  #     "subtitle": "人間の業",
+  #     "series": "芥川全集",
+  #     "author": "芥川龍之介",
+  #     "orignal_author": "芥川龍之介",
+  #     "illustrator": "奥山誠司",
+  #     "translator": "ジェイ・ルービン",
+  #     "supervisor": "角川春樹",
+  #     "publisher": "角川文庫",
+  #     "pubdate": "2012/01/20",
+  #     "cover_uri": "https://cover.openbd.jp/9784107723369.jpg",
+  #     "summary": "羅城門は芥川の書いた作品の...",
+  #     "book_rank": 5
+  #   }
+  get '/:version/book_collections/:isbn' do
+    begin
+      token = params[:token]
+      isbn = params[:isbn]
+      id = @token.getId(token)
+      response = @book_manager.getBookCollect(isbn, id)
+      json response
+
+    rescue TokenManager::UnknownTokenError,
+           TokenManager::ExpiredTokenError
+
+      # トークン認証失敗
+      raise WebError.new(:status => 408, :code => "E09001",
+                         :message => "Session expired of #{token}")
+
+    rescue BookManager::NotFoundInstanceError
+
+      # 対象の書籍が存在しない
+      raise WebError.new(:status => 404, :code => "E09002",
+                         :message => "Not found target book. isbn=#{isbn}")
+
+    end
+  end
+  
+  # ユーザ所持の蔵書更新
+  # @path_param [String] version API Version
+  # @path_param [String] isbn 対象書籍のISBN
+  # @query_param [String] tonken APIアクセス用トークン
+  # @example Request Body
+  #   {
+  #     "title": "羅生門",
+  #     "volume": 0,
+  #     "subtitle": "人間の業",
+  #     "series": "芥川全集",
+  #     "author": "芥川龍之介",
+  #     "orignal_author": "芥川龍之介",
+  #     "illustrator": "奥山誠司",
+  #     "translator": "ジェイ・ルービン",
+  #     "supervisor": "角川春樹",
+  #     "publisher": "角川文庫",
+  #     "pubdate": "2012/01/20",
+  #     "cover_uri": "https://cover.openbd.jp/9784107723369.jpg",
+  #     "cover_data": "data:image/png;base64,q/9j/4AAQSkZJRgABAQAAAQABAAD...",
+  #     "summary": "羅城門は芥川の書いた作品の...",
+  #     "book_rank": 5
+  #   }
+  put '/:version/book_collections/:isbn' do
+    begin
+      token = params[:token]
+      isbn = params[:isbn]
+      id = @token.getId(token)
+      book = JSON.parse(request.body.read)
+      @book_manager.updateCollectBook(id, book)
+      
+      response = {}
+      json response
+
+    rescue TokenManager::UnknownTokenError,
+           TokenManager::ExpiredTokenError
+
+      #トークン認証失敗
+      raise WebError.new(:status => 408, :code => "E10001",
+                         :message => "Session expired of #{token}")
+
+    rescue BookManager::NotFoundInstanceError
+
+      # 対象の書籍が存在しない
+      raise WebError.new(:status => 404, :code => "E10002",
+                         :message => "Not found target book. isbn=#{isbn}")
+
+    rescue JSON::ParserError => e
+      
+      # JSONパースエラー
+      raise WebError.new(:status => 400, :code => 'E10003',
+                         :message => "Failed to parse JSON. #{e.message}")
+    end
+  end
+
+  # ユーザ所持の蔵書詳細取得
+  # @path_param [String] version API Version
+  # @path_param [String] isbn 対象書籍のISBN
+  # @query_param [String] tonken APIアクセス用トークン
+  delete '/:version/book_collections/:isbn' do
+    begin
+      token = params[:token]
+      isbn = params[:isbn]
+      id = @token.getId(token)
+      @book_manager.deleteBookCollect(isbn, id)
+      response = {}
+      json response
+
+    rescue TokenManager::UnknownTokenError,
+           TokenManager::ExpiredTokenError
+
+      # トークン認証失敗
+      raise WebError.new(:status => 408, :code => "E11001",
+                         :message => "Session expired of #{token}")
+
+    rescue BookManager::NotFoundInstanceError
+
+      # 対象の書籍が存在しない
+      raise WebError.new(:status => 404, :code => "E11002",
+                         :message => "Not found target book. isbn=#{isbn}")
+
     end
   end
   
+  # 登録書籍の一覧取得
+  # @path_param [String] version API Version
+  # @query_param [String] tonken APIアクセス用トークン
+  # @query_param [Boolean] nobody 所有者がいない書籍の一覧取得時に true
+  # @example Response Body
+  #   [
+  #     {
+  #       "isbn": "978412345678",
+  #       "title": "羅生門",
+  #       "volume": 0,
+  #       "author": "芥川龍之介",
+  #       "orignal_author": "芥川龍之介",
+  #       "illustrator": "奥山誠司",
+  #       "publisher": "角川文庫",
+  #       "pubdate": "2012/01/20"
+  #     }
+  #   ]
+  get '/:version/books' do
+    begin
+      token = params[:token]
+      nobody = params[:nobody]
+      id = @token.getId(token)
+      # 管理者権限でなければ利用できない
+      raise WebError.new(:status => 403, :code => "E12001",
+                         :message => "User is  without authority. userid=#{id}"
+                        ) unless @user_account.checkRole(id, [User::ROLE_ADMIN])
+      book_list = @book_manager.getBookList(nobody == true)
+      response = book_list.map { |book|
+        {
+          :isbn => book.isbn, :title => book.title, :volume => book.volume,
+          :author => book.author, :original_author => book.original_author,
+          :illustrator => book.illustrator, :publisher => book.publisher,
+          :pubdate => book.pubdate,
+        }
+      }
+      json response
+      
+    rescue TokenManager::UnknownTokenError,
+           TokenManager::ExpiredTokenError
+      
+      # トークン認証失敗
+      raise WebError.new(:status => 408, :code => "E12002",
+                         :message => "Session expired of #{token}")
+      
+    end
+  end
+
+  # 登録書籍の詳細取得
+  # @path_param [String] version API Version
+  # @path_param [String] isbn 対象書籍のISBN
+  # @query_param [String] tonken APIアクセス用トークン
+  # @example Response Body
+  #   {
+  #     "isbn": "978412345678",
+  #     "title": "羅生門",
+  #     "volume": 0,
+  #     "subtitle": "人間の業",
+  #     "series": "芥川全集",
+  #     "author": "芥川龍之介",
+  #     "orignal_author": "芥川龍之介",
+  #     "illustrator": "奥山誠司",
+  #     "translator": "ジェイ・ルービン",
+  #     "supervisor": "角川春樹",
+  #     "publisher": "角川文庫",
+  #     "pubdate": "2012/01/20",
+  #     "cover_uri": "https://cover.openbd.jp/9784107723369.jpg"
+  #   }
+  get '/:version/books/:isbn' do
+    begin
+      token = params[:token]
+      isbn = params[:isbn]
+      id = @token.getId(token)
+      # 管理者権限でなければ利用できない
+      raise WebError.new(:status => 403, :code => "E13001",
+                         :message => "User is  without authority. userid=#{id}"
+                        ) unless @user_account.checkRole(id, [User::ROLE_ADMIN])
+      response = @book_manager.getBook(isbn)
+      json response
+      
+    rescue TokenManager::UnknownTokenError,
+           TokenManager::ExpiredTokenError
+      
+      # トークン認証失敗
+      raise WebError.new(:status => 408, :code => "E13002",
+                         :message => "Session expired of #{token}")
+      
+    rescue BookManager::NotFoundInstanceError
+
+      # 対象の書籍が存在しない
+      raise WebError.new(:status => 404, :code => "E13002",
+                         :message => "Not found target book. isbn=#{isbn}")
+
+    end
+  end
+
+    # 登録書籍の詳細取得
+  # @path_param [String] version API Version
+  # @path_param [String] isbn 対象書籍のISBN
+  # @query_param [String] tonken APIアクセス用トークン
+  # @example Response Body
+  #   {
+  #     "isbn": "978412345678",
+  #     "title": "羅生門",
+  #     "volume": 0,
+  #     "subtitle": "人間の業",
+  #     "series": "芥川全集",
+  #     "author": "芥川龍之介",
+  #     "orignal_author": "芥川龍之介",
+  #     "illustrator": "奥山誠司",
+  #     "translator": "ジェイ・ルービン",
+  #     "supervisor": "角川春樹",
+  #     "publisher": "角川文庫",
+  #     "pubdate": "2012/01/20",
+  #     "cover_uri": "https://cover.openbd.jp/9784107723369.jpg"
+  #   }
+  delete '/:version/books/:isbn' do
+    begin
+      token = params[:token]
+      isbn = params[:isbn]
+      id = @token.getId(token)
+      # 管理者権限でなければ利用できない
+      raise WebError.new(:status => 403, :code => "E13001",
+                         :message => "User is  without authority. userid=#{id}"
+                        ) unless @user_account.checkRole(id, [User::ROLE_ADMIN])
+      response = @book_manager.deleteBook(isbn)
+      json response
+      
+    rescue TokenManager::UnknownTokenError,
+           TokenManager::ExpiredTokenError
+      
+      # トークン認証失敗
+      raise WebError.new(:status => 408, :code => "E14002",
+                         :message => "Session expired of #{token}")
+      
+    rescue BookManager::NotFoundInstanceError
+
+      # 対象の書籍が存在しない
+      raise WebError.new(:status => 404, :code => "E14002",
+                         :message => "Not found target book. isbn=#{isbn}")
+
+    end
+  end
+
   # エラー時のレスポンス生成処理
   error WebError do
     e = env['sinatra.error']
+    logger.error("Failed Access error. #{e.params}")
     status e.params[:status]
-    response = {:code =>  e.params[:code]}
+    response = {:code =>  e.params[:code], :message => e.params[:message]}
     json response
   end
 end
index 28eb7ad..ec09a3e 100644 (file)
@@ -2,7 +2,7 @@
 # frozen_string_literal: true
 
 # SPA用コントローラ
-# @auth OHASHI, Norikazu
+# @author OHASHI, Norikazu
 
 require 'rack/cache'
 require 'sinatra/base'
@@ -10,6 +10,7 @@ require 'sinatra'
 require 'haml'
 require 'logger'
 
+# 書籍管理サーバ SPAGUI Controller
 class SPAGui < Sinatra::Base
   set :root, File.join(File.dirname(__FILE__), '..')
   set :views, proc { File.join(root, 'views') }
index 66fca8a..74710d5 100644 (file)
@@ -120,17 +120,17 @@ class WebGui < Sinatra::Base
     end
   end
 
-  # スタイルシート
-  get '/css/style.css' do
-    cache_control :public, :must_revalidate, :max_age => 120
-    scss :'scss/style'
-  end
-
-  # javaScript
-  get '/scripts/check_form.js' do
-    cache_control :public, :must_revalidate, :max_age => 120
-    coffee :'coffee/check_form'
-  end
+  # スタイルシート
+  get '/css/style.css' do
+    cache_control :public, :must_revalidate, :max_age => 120
+    scss :'scss/style'
+  end
+
+  # javaScript
+  get '/scripts/check_form.js' do
+    cache_control :public, :must_revalidate, :max_age => 120
+    coffee :'coffee/check_form'
+  end
 
   # メインページ
   get '/' do
index 8f186e1..9d59ac7 100644 (file)
@@ -65,7 +65,7 @@ class BookFull < ActiveRecord::Base
   end
 end
 
-# ユーザ管理
+# 書籍情報管理
 class BookManager
   include Singleton
   
@@ -494,6 +494,66 @@ class BookManager
     end
   end
 
+  # 書籍一覧取得
+  # @param [Boolean] nobody 所有者無しのみを検索
+  def getBookList(nobody=false)
+    book_list = Book.all
+    if nobody
+      # BookCollection に該当 ISBN が含まれていない書籍だけを選別
+      books = []
+      book_list.each do |book|
+        book_collects = BookCollection.where(:isbn => book.isbn)
+        if (book_collects.empty?)
+          books.push(book)
+        end
+      end
+      return books
+    else
+      return book_list
+    end
+  end
+
+  def createCollectBook(id, book)
+    isbn = book[:isbn]
+    book_info, summary, book_rank, cover_data = makeBookInfo(book)
+    
+    Book.transcation do
+      if isBook(isbn)
+        book_info.delete(:isbn)
+        updateBook(isbn, book_info)
+      else
+        createBook(book_info)
+      end
+      if !cover_data.nil?
+        BookCover.transcation do
+          cover_image = setBookImage(cover_data)
+          image_hash = addBookCover(isbn, cover_image)
+          cover_uri = "/book_image/#{image_hash}"
+          updateBook(isbn, :cover_uri => cover_uri)
+        end
+      end
+      createBookCollect(isbn, id, summary, book_rank)
+    end
+  end
+
+  def updateCollectBook(id, book)
+    isbn = book[:isbn]
+    book_info, summary, book_rank, cover_data = makeBookInfo(book)
+    
+    Book.transcation do
+      updateBook(isbn, book_info)
+      if !cover_data.nil?
+        BookCover.transcation do
+          cover_image = setBookImage(cover_data)
+          image_hash = addBookCover(isbn, cover_image)
+          cover_uri = "/book_image/#{image_hash}"
+          updateBook(isbn, :cover_uri => cover_uri)
+        end
+      end
+      updateBookCollect(isbn, id, summary, book_rank)
+    end
+  end
+
   private
   # ISBNの文字列から "-" を削除
   # @param [String] isbn 処理対象のISBN
@@ -777,4 +837,32 @@ class BookManager
       :book_rank => book_collect[:book_rank]
     }
   end
+
+  # 更新用の書籍情報を作成する。
+  # @param [Hash] params Postで取得した全パラメータ
+  # @return [Hash] 更新用書籍情報
+  def makeBookInfo(book)
+    book_info = {}
+    book.each do |key, value|
+      case key
+      when 'summary', 'book_rank', 'cover_data' then
+        # 対象キーは書籍情報ではないので飛す
+        next
+      end
+      book_info[key.to_sym] = value
+    end
+    return book_info, book[:summary], book[:book_runk], book[:cover_data]
+  end
+
+  # 書影情報の作成
+  # @param [String] cover_data 書影データのData URI scheme(bas64)
+  # @return [Hash] 作成した書影データ
+  def setBookImage(cover_data)
+    cover_image = {}
+    # 書影情報の取得
+    data_uri = URI::Data.new(cover_data)
+    cover_image[:mime_type] = data_uri.content_type
+    cover_image[:data] =  data_uri.data
+    return cover_image
+  end
 end
index cdc217b..001644d 100644 (file)
@@ -14,16 +14,20 @@ ActiveRecord::Base.establish_connection(:development)
 
 # トークン情報
 class Token < ActiveRecord::Base
+
+  # トークンが期限切れかを確認する。
   def expired?
     self.deadline < Time.now
   end
 
+  # トークン切れのセッション一覧を取得する。
   def self.expired_items
     all.select {|d| d.deadline < Time.now}
   end
   
 end
 
+# セッショントークン管理
 class TokenManager
   include Singleton
 
@@ -77,7 +81,7 @@ class TokenManager
   # @param [String] token 確認対象のトークン
   # @return [Integer] トークンを所持するユーザID
   # @raise [UnknownTokenError] 対象のトークンを保持していない
-  # @raise [ExpiredTokenError] 対のトークンが期限切れ
+  # @raise [ExpiredTokenError] 対のトークンが期限切れ
   def getId(token)
     begin
       token_item = Token.find_by(:token => token)
index c5e82b3..c6580c3 100644 (file)
@@ -178,9 +178,7 @@ class UserAccount
   end
 
   # ユーザ一覧の取得
-  # @param [Integer] id 取得対象ユーザID
   # @return [Array<User>] 全ユーザ情報の一覧
-  # @raise [AuthenticationError] ユーザ権限不正
   def getUserList
     return User.all
   end
index c11f00b..61494d5 100644 (file)
   %body
       
     - if  user_name.nil?
-      #main1= yield
+      #main1
+        != yield
     - else
       #head
         #{user_name} 蔵書管理 Home
       #center
         #sidebar
-          = haml :sidebar
-        #main2= yield
+          != haml :sidebar
+        #main2
+          != yield
     #foot
       %p
         連絡先: