diff --git a/Gemfile b/Gemfile index dabdacc..03c3a2c 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,7 @@ gem 'mongoid', '~> 7.0.5' gem 'rackup', '~> 2.1.0' gem 'rspec', '~> 3.10' gem 'sinatra', '~> 4.0.0' +gem 'sinatra-contrib', '~> 4.0.0' # Development & Testing group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index f67cf05..ca3a3ab 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -27,6 +27,7 @@ GEM mongoid (7.0.13) activemodel (>= 5.1, < 6.2) mongo (>= 2.7.0, < 3.0.0) + multi_json (1.17.0) mustermann (3.0.0) ruby2_keywords (~> 0.0.1) parallel (1.27.0) @@ -102,6 +103,12 @@ GEM rack-protection (= 4.0.0) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) + sinatra-contrib (4.0.0) + multi_json (>= 0.0.2) + mustermann (~> 3.0) + rack-protection (= 4.0.0) + sinatra (= 4.0.0) + tilt (~> 2.0) tilt (2.3.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) @@ -125,6 +132,7 @@ DEPENDENCIES rubocop-rspec (~> 2.25) simplecov (~> 0.22) sinatra (~> 4.0.0) + sinatra-contrib (~> 4.0.0) RUBY VERSION ruby 3.2.2p53 diff --git a/main.rb b/main.rb index cd41538..7ff7dbb 100644 --- a/main.rb +++ b/main.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'sinatra' +require 'sinatra/namespace' require 'json' require 'mongoid' require 'dotenv/load' @@ -18,66 +19,73 @@ 'Hello to ChainForge!' end -post '/chain' do - blockchain = Blockchain.create - blockchain.save! - { id: blockchain.id }.to_json -end - -post '/chain/:id/block' do - block_data = parse_json_body - chain_id = params[:id] - blockchain = find_block_chain(chain_id) - difficulty = validate_difficulty(block_data['difficulty']) - block = blockchain.add_block(block_data['data'], difficulty: difficulty) - - { - chain_id: chain_id, - block_id: block.id.to_s, - block_hash: block._hash, - nonce: block.nonce, - difficulty: block.difficulty - }.to_json -end +# API v1 +namespace '/api/v1' do + before do + content_type :json + end -post '/chain/:id/block/:block_id/valid' do - block_data = parse_json_body - chain_id = params[:id] - block_id = params[:block_id] - blockchain = find_block_chain(chain_id) - block = blockchain.blocks.find(block_id) - raise 'Block not found' unless block - - valid = block.valid_data?(block_data['data']) - - { - chain_id: chain_id, - block_id: block.id.to_s, - valid: valid - }.to_json -end + post '/chain' do + blockchain = Blockchain.create + blockchain.save! + { id: blockchain.id }.to_json + end -get '/chain/:id/block/:block_id' do - chain_id = params[:id] - block_id = params[:block_id] - blockchain = find_block_chain(chain_id) - block = blockchain.blocks.find(block_id) - raise 'Block not found' unless block - - { - chain_id: chain_id, - block: { - id: block.id.to_s, - index: block.index, - data: block.data, - hash: block._hash, - previous_hash: block.previous_hash, + post '/chain/:id/block' do + block_data = parse_json_body + chain_id = params[:id] + blockchain = find_block_chain(chain_id) + difficulty = validate_difficulty(block_data['difficulty']) + block = blockchain.add_block(block_data['data'], difficulty: difficulty) + + { + chain_id: chain_id, + block_id: block.id.to_s, + block_hash: block._hash, nonce: block.nonce, - difficulty: block.difficulty, - timestamp: block.created_at.to_i, - valid_hash: block.valid_hash? - } - }.to_json + difficulty: block.difficulty + }.to_json + end + + post '/chain/:id/block/:block_id/valid' do + block_data = parse_json_body + chain_id = params[:id] + block_id = params[:block_id] + blockchain = find_block_chain(chain_id) + block = blockchain.blocks.find(block_id) + raise 'Block not found' unless block + + valid = block.valid_data?(block_data['data']) + + { + chain_id: chain_id, + block_id: block.id.to_s, + valid: valid + }.to_json + end + + get '/chain/:id/block/:block_id' do + chain_id = params[:id] + block_id = params[:block_id] + blockchain = find_block_chain(chain_id) + block = blockchain.blocks.find(block_id) + raise 'Block not found' unless block + + { + chain_id: chain_id, + block: { + id: block.id.to_s, + index: block.index, + data: block.data, + hash: block._hash, + previous_hash: block.previous_hash, + nonce: block.nonce, + difficulty: block.difficulty, + timestamp: block.created_at.to_i, + valid_hash: block.valid_hash? + } + }.to_json + end end helpers do diff --git a/spec/api_spec.rb b/spec/api_spec.rb index 3bf65e2..87f1047 100644 --- a/spec/api_spec.rb +++ b/spec/api_spec.rb @@ -19,9 +19,9 @@ def app end end - describe 'POST /chain' do + describe 'POST /api/v1/chain' do it 'creates a new blockchain' do - post '/chain' + post '/api/v1/chain' expect(last_response).to be_ok expect(last_response.content_type).to include('application/json') @@ -31,12 +31,12 @@ def app end end - describe 'POST /chain/:id/block' do + describe 'POST /api/v1/chain/:id/block' do let(:blockchain) { Blockchain.create! } let(:block_data) { { data: 'Test Block Data' } } it 'adds a new block to the blockchain' do - post "/chain/#{blockchain.id}/block", block_data.to_json, { 'CONTENT_TYPE' => 'application/json' } + post "/api/v1/chain/#{blockchain.id}/block", block_data.to_json, { 'CONTENT_TYPE' => 'application/json' } expect(last_response).to be_ok expect(last_response.content_type).to include('application/json') @@ -45,21 +45,23 @@ def app expect(json['chain_id']).to eq(blockchain.id.to_s) expect(json['block_id']).not_to be_nil expect(json['block_hash']).not_to be_nil + expect(json['nonce']).not_to be_nil + expect(json['difficulty']).to eq(2) end it 'returns error when chain not found' do - post '/chain/invalid_id/block', block_data.to_json, { 'CONTENT_TYPE' => 'application/json' } + post '/api/v1/chain/invalid_id/block', block_data.to_json, { 'CONTENT_TYPE' => 'application/json' } expect(last_response.status).to eq(500) # rubocop:disable RSpecRails/HaveHttpStatus end end - describe 'POST /chain/:id/block/:block_id/valid' do + describe 'POST /api/v1/chain/:id/block/:block_id/valid' do let(:blockchain) { Blockchain.create! } let(:block_data) { { data: 'Validation Test Data' } } let!(:block) { blockchain.add_block(block_data[:data]) } it 'validates block with correct data' do - post "/chain/#{blockchain.id}/block/#{block.id}/valid", + post "/api/v1/chain/#{blockchain.id}/block/#{block.id}/valid", block_data.to_json, { 'CONTENT_TYPE' => 'application/json' } @@ -70,7 +72,7 @@ def app it 'invalidates block with incorrect data' do invalid_data = { data: 'Wrong Data' } - post "/chain/#{blockchain.id}/block/#{block.id}/valid", + post "/api/v1/chain/#{blockchain.id}/block/#{block.id}/valid", invalid_data.to_json, { 'CONTENT_TYPE' => 'application/json' } @@ -79,4 +81,24 @@ def app expect(json['valid']).to be false end end + + describe 'GET /api/v1/chain/:id/block/:block_id' do + let(:blockchain) { Blockchain.create! } + let(:block_data) { 'GET Block Test Data' } + let!(:block) { blockchain.add_block(block_data) } + + it 'retrieves block details with mining information' do + get "/api/v1/chain/#{blockchain.id}/block/#{block.id}" + + expect(last_response).to be_ok + json = JSON.parse(last_response.body) + + expect(json['chain_id']).to eq(blockchain.id.to_s) + expect(json['block']['id']).to eq(block.id.to_s) + expect(json['block']['data']).to eq(block_data) + expect(json['block']['nonce']).not_to be_nil + expect(json['block']['difficulty']).to eq(2) + expect(json['block']['valid_hash']).to be true + end + end end