diff --git a/app/controllers/api/v2/metacodes_controller.rb b/app/controllers/api/v2/metacodes_controller.rb new file mode 100644 index 00000000..71cd4a50 --- /dev/null +++ b/app/controllers/api/v2/metacodes_controller.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +module Api + module V2 + class MetacodesController < RestfulController + def searchable_columns + [:name] + end + end + end +end diff --git a/app/controllers/api/v2/restful_controller.rb b/app/controllers/api/v2/restful_controller.rb index f837957d..5d8f81b3 100644 --- a/app/controllers/api/v2/restful_controller.rb +++ b/app/controllers/api/v2/restful_controller.rb @@ -70,7 +70,8 @@ module Api def default_scope { - embeds: embeds + embeds: embeds, + current_user: current_user } end diff --git a/app/controllers/api/v2/users_controller.rb b/app/controllers/api/v2/users_controller.rb new file mode 100644 index 00000000..9eba232f --- /dev/null +++ b/app/controllers/api/v2/users_controller.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +module Api + module V2 + class UsersController < RestfulController + def current + @user = current_user + authorize @user + return show + end + + private + + def searchable_columns + [:name] + end + + # only ask serializer to return is_admin field if we're on the + # current_user action + def default_scope + super.merge(show_is_admin: action_name == 'current') + end + end + end +end diff --git a/app/policies/metacode_policy.rb b/app/policies/metacode_policy.rb new file mode 100644 index 00000000..e8787f8d --- /dev/null +++ b/app/policies/metacode_policy.rb @@ -0,0 +1,27 @@ +class MetacodePolicy < ApplicationPolicy + def index? + true + end + + def show? + true + end + + def create? + user.is_admin + end + + def update? + user.is_admin + end + + def destroy? + false + end + + class Scope < Scope + def resolve + scope.all + end + end +end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb new file mode 100644 index 00000000..fa6158b8 --- /dev/null +++ b/app/policies/user_policy.rb @@ -0,0 +1,41 @@ +class UserPolicy < ApplicationPolicy + def index? + user.present? + end + + def show? + user.present? + end + + def create? + fail 'Create should be handled by Devise' + end + + def update? + user == record + end + + def destroy? + false + end + + def details? + show? + end + + def updatemetacodes? + update? + end + + # API action + def current? + user == record + end + + class Scope < Scope + def resolve + return scope.all if user.present? + scope.none + end + end +end diff --git a/app/serializers/api/v2/metacode_serializer.rb b/app/serializers/api/v2/metacode_serializer.rb index 16013e33..0b1ac553 100644 --- a/app/serializers/api/v2/metacode_serializer.rb +++ b/app/serializers/api/v2/metacode_serializer.rb @@ -4,9 +4,8 @@ module Api class MetacodeSerializer < ApplicationSerializer attributes :id, :name, - :manual_icon, :color, - :aws_icon + :icon end end end diff --git a/app/serializers/api/v2/user_serializer.rb b/app/serializers/api/v2/user_serializer.rb index ec58775d..3234205e 100644 --- a/app/serializers/api/v2/user_serializer.rb +++ b/app/serializers/api/v2/user_serializer.rb @@ -5,9 +5,11 @@ module Api attributes :id, :name, :avatar, - :is_admin, :generation + attribute :is_admin, + if: -> { scope[:show_is_admin] && scope[:current_user] == object } + def avatar object.image.url(:sixtyfour) end diff --git a/config/routes.rb b/config/routes.rb index 41dd40c4..76158105 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -63,13 +63,17 @@ Metamaps::Application.routes.draw do namespace :api, path: '/api', default: { format: :json } do namespace :v2, path: '/v2' do + resources :metacodes, only: [:index, :show] + resources :mappings, only: [:index, :create, :show, :update, :destroy] resources :maps, only: [:index, :create, :show, :update, :destroy] resources :synapses, only: [:index, :create, :show, :update, :destroy] - resources :topics, only: [:index, :create, :show, :update, :destroy] - resources :mappings, only: [:index, :create, :show, :update, :destroy] resources :tokens, only: [:create, :destroy] do get :my_tokens, on: :collection end + resources :topics, only: [:index, :create, :show, :update, :destroy] + resources :users, only: [:index, :show] do + get :current, on: :collection + end end namespace :v1, path: '/v1' do # api v1 routes all lead to a deprecation error method diff --git a/doc/api/api.raml b/doc/api/api.raml index e59ae8d3..624e5a46 100644 --- a/doc/api/api.raml +++ b/doc/api/api.raml @@ -16,19 +16,23 @@ traits: searchable: !include traits/searchable.raml schemas: - topic: !include schemas/_topic.json - synapse: !include schemas/_synapse.json map: !include schemas/_map.json mapping: !include schemas/_mapping.json + metacode: !include schemas/_metacode.json + synapse: !include schemas/_synapse.json token: !include schemas/_token.json + topic: !include schemas/_topic.json + user: !include schemas/_user.json #resourceTypes: # base: !include resourceTypes/base.raml # item: !include resourceTypes/item.raml # collection: !include resourceTypes/collection.raml -/topics: !include apis/topics.raml -/synapses: !include apis/synapses.raml /maps: !include apis/maps.raml /mappings: !include apis/mappings.raml +/metacodes: !include api/metacodes.raml +/synapses: !include apis/synapses.raml /tokens: !include apis/tokens.raml +/topics: !include apis/topics.raml +/users: !include apis/users.raml diff --git a/doc/api/apis/metacodes.raml b/doc/api/apis/metacodes.raml new file mode 100644 index 00000000..37cbd17a --- /dev/null +++ b/doc/api/apis/metacodes.raml @@ -0,0 +1,16 @@ +#type: collection +get: + is: [ searchable: { searchFields: "name" }, orderable, pageable ] + responses: + 200: + body: + application/json: + example: !include ../examples/metacodes.json +/{id}: + #type: item + get: + responses: + 200: + body: + application/json: + example: !include ../examples/metacode.json diff --git a/doc/api/apis/users.raml b/doc/api/apis/users.raml new file mode 100644 index 00000000..7f421059 --- /dev/null +++ b/doc/api/apis/users.raml @@ -0,0 +1,24 @@ +#type: collection +get: + is: [ searchable: { searchFields: "name" }, orderable, pageable ] + responses: + 200: + body: + application/json: + example: !include ../examples/users.json +/current: + #type: item + get: + responses: + 200: + body: + application/json: + example: !include ../examples/current_user.json +/{id}: + #type: item + get: + responses: + 200: + body: + application/json: + example: !include ../examples/user.json diff --git a/doc/api/examples/metacode.json b/doc/api/examples/metacode.json new file mode 100644 index 00000000..506a10c0 --- /dev/null +++ b/doc/api/examples/metacode.json @@ -0,0 +1,8 @@ +{ + "data": { + "id": 1, + "name": "Action", + "color": "#BD6C85", + "icon": "https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_action.png" + } +} diff --git a/doc/api/examples/metacodes.json b/doc/api/examples/metacodes.json new file mode 100644 index 00000000..8e06f56c --- /dev/null +++ b/doc/api/examples/metacodes.json @@ -0,0 +1,30 @@ +{ + "data": [ + { + "id": 1, + "name": "Action", + "color": "#BD6C85", + "icon": "https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_action.png" + }, + { + "id": 2, + "name": "Activity", + "color": "#6EBF65", + "icon": "https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_activity.png" + }, + { + "id": 3, + "name": "Catalyst", + "color": "#EF8964", + "icon": "https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_catalyst.png" + } + ], + "page": { + "current_page": 1, + "next_page": 2, + "prev_page": 0, + "total_pages": 16, + "total_count": 47, + "per": 3 + } +} diff --git a/doc/api/examples/user.json b/doc/api/examples/user.json new file mode 100644 index 00000000..83476006 --- /dev/null +++ b/doc/api/examples/user.json @@ -0,0 +1,9 @@ +{ + "data": { + "id": 1, + "name": "user", + "avatar": "https://s3.amazonaws.com/metamaps-assets/site/user.png", + "generation": 0, + "is_admin": false + } +} diff --git a/doc/api/examples/users.json b/doc/api/examples/users.json new file mode 100644 index 00000000..944f631e --- /dev/null +++ b/doc/api/examples/users.json @@ -0,0 +1,24 @@ +{ + "data": [ + { + "id": 1, + "name": "user", + "avatar": "https://s3.amazonaws.com/metamaps-assets/site/user.png", + "generation": 0 + }, + { + "id": 2, + "name": "admin", + "avatar": "https://s3.amazonaws.com/metamaps-assets/site/user.png", + "generation": 0 + } + ], + "page": { + "current_page": 1, + "next_page": 0, + "prev_page": 0, + "total_pages": 1, + "total_count": 2, + "per": 25 + } +} diff --git a/doc/api/schemas/_metacode.json b/doc/api/schemas/_metacode.json new file mode 100644 index 00000000..cc6b4f76 --- /dev/null +++ b/doc/api/schemas/_metacode.json @@ -0,0 +1,24 @@ +{ + "name": "Metacode", + "type": "object", + "properties": { + "id": { + "$ref": "_id.json" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "icon": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "color", + "icon" + ] +} diff --git a/doc/api/schemas/_user.json b/doc/api/schemas/_user.json new file mode 100644 index 00000000..e5805251 --- /dev/null +++ b/doc/api/schemas/_user.json @@ -0,0 +1,28 @@ +{ + "name": "User", + "type": "object", + "properties": { + "id": { + "$ref": "_id.json" + }, + "name": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "generation": { + "type": "integer", + "minimum": 0 + }, + "is_admin": { + "type": "boolean" + } + }, + "required": [ + "id", + "name", + "avatar", + "generation" + ] +} diff --git a/doc/api/schemas/metacode.json b/doc/api/schemas/metacode.json new file mode 100644 index 00000000..c5fa7106 --- /dev/null +++ b/doc/api/schemas/metacode.json @@ -0,0 +1,12 @@ +{ + "name": "Metacode Envelope", + "type": "object", + "properties": { + "data": { + "$ref": "_metacode.json" + } + }, + "required": [ + "data" + ] +} diff --git a/doc/api/schemas/metacodes.json b/doc/api/schemas/metacodes.json new file mode 100644 index 00000000..c3869366 --- /dev/null +++ b/doc/api/schemas/metacodes.json @@ -0,0 +1,19 @@ +{ + "name": "Metacodes", + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "_metacode.json" + } + }, + "page": { + "$ref": "_page.json" + } + }, + "required": [ + "data", + "page" + ] +} diff --git a/doc/api/schemas/user.json b/doc/api/schemas/user.json new file mode 100644 index 00000000..a5c9d490 --- /dev/null +++ b/doc/api/schemas/user.json @@ -0,0 +1,12 @@ +{ + "name": "User Envelope", + "type": "object", + "properties": { + "data": { + "$ref": "_user.json" + } + }, + "required": [ + "data" + ] +} diff --git a/doc/api/schemas/users.json b/doc/api/schemas/users.json new file mode 100644 index 00000000..6cae2b80 --- /dev/null +++ b/doc/api/schemas/users.json @@ -0,0 +1,19 @@ +{ + "name": "Users", + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "_user.json" + } + }, + "page": { + "$ref": "_page.json" + } + }, + "required": [ + "data", + "page" + ] +} diff --git a/spec/api/v2/metacodes_api_spec.rb b/spec/api/v2/metacodes_api_spec.rb new file mode 100644 index 00000000..67d3d543 --- /dev/null +++ b/spec/api/v2/metacodes_api_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true +require 'rails_helper' + +RSpec.describe 'metacodes API', type: :request do + let(:user) { create(:user, admin: true) } + let(:token) { create(:token, user: user).token } + let(:metacode) { create(:metacode) } + + it 'GET /api/v2/metacodes' do + create_list(:metacode, 5) + get '/api/v2/metacodes', params: { access_token: token } + + expect(response).to have_http_status(:success) + expect(response).to match_json_schema(:metacodes) + expect(JSON.parse(response.body)['data'].count).to eq 5 + end + + it 'GET /api/v2/metacodes/:id' do + get "/api/v2/metacodes/#{metacode.id}", params: { access_token: token } + + expect(response).to have_http_status(:success) + expect(response).to match_json_schema(:metacode) + expect(JSON.parse(response.body)['data']['id']).to eq metacode.id + end +end diff --git a/spec/api/v2/users_api_spec.rb b/spec/api/v2/users_api_spec.rb new file mode 100644 index 00000000..70e22c2f --- /dev/null +++ b/spec/api/v2/users_api_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true +require 'rails_helper' + +RSpec.describe 'users API', type: :request do + let(:user) { create(:user, admin: true) } + let(:token) { create(:token, user: user).token } + let(:record) { create(:user) } + + it 'GET /api/v2/users' do + create_list(:user, 5) + get '/api/v2/users', params: { access_token: token } + + expect(response).to have_http_status(:success) + expect(response).to match_json_schema(:users) + expect(JSON.parse(response.body)['data'].count).to eq 6 + end + + it 'GET /api/v2/users/:id' do + get "/api/v2/users/#{record.id}", params: { access_token: token } + + expect(response).to have_http_status(:success) + expect(response).to match_json_schema(:user) + expect(JSON.parse(response.body)['data']['id']).to eq record.id + end + + it 'GET /api/v2/users/current' do + get '/api/v2/users/current', params: { access_token: token } + + expect(response).to have_http_status(:success) + expect(response).to match_json_schema(:user) + expect(JSON.parse(response.body)['data']['id']).to eq user.id + end +end