diff --git a/app/controllers/api/v2/stars_controller.rb b/app/controllers/api/v2/stars_controller.rb new file mode 100644 index 00000000..8b62ee36 --- /dev/null +++ b/app/controllers/api/v2/stars_controller.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true +module Api + module V2 + class StarsController < RestfulController + skip_before_action :load_resource + + def create + @map = Map.find(params[:id]) + @star = Star.new(user: current_user, map: @map) + authorize @map, :star? + create_action + + if @star.errors.empty? + render json: @map, scope: default_scope, serializer: MapSerializer, root: serializer_root + else + respond_with_errors + end + end + + def destroy + @map = Map.find(params[:id]) + authorize @map, :unstar? + @star = @map.stars.find_by(user: current_user) + @star.destroy if @star.present? + head :no_content + end + end + end +end diff --git a/app/models/map.rb b/app/models/map.rb index 609b1be4..a8e9c866 100644 --- a/app/models/map.rb +++ b/app/models/map.rb @@ -39,15 +39,8 @@ class Map < ApplicationRecord Perm.short(permission) end - # return an array of the contributors to the map def contributors - contributors = [] - - mappings.each do |m| - contributors.push(m.user) unless contributors.include?(m.user) - end - - contributors + mappings.map(&:user).uniq end def editors @@ -88,6 +81,10 @@ class Map < ApplicationRecord updated_at.strftime('%m/%d/%Y') end + def starred_by_user?(user) + user.stars.where(map: self).exists? + end + def as_json(_options = {}) json = super(methods: [:user_name, :user_image, :topic_count, :synapse_count, :contributor_count, :collaborator_ids, :screenshot_url], except: [:screenshot_content_type, :screenshot_file_size, :screenshot_file_name, :screenshot_updated_at]) json[:created_at_clean] = created_at_str diff --git a/app/models/star.rb b/app/models/star.rb index dcaaa559..a49ae2b1 100644 --- a/app/models/star.rb +++ b/app/models/star.rb @@ -2,4 +2,5 @@ class Star < ActiveRecord::Base belongs_to :user belongs_to :map + validates :map, uniqueness: { scope: :user, message: 'You have already starred this map' } end diff --git a/app/serializers/api/v2/map_serializer.rb b/app/serializers/api/v2/map_serializer.rb index 0a0be2c0..ff641c69 100644 --- a/app/serializers/api/v2/map_serializer.rb +++ b/app/serializers/api/v2/map_serializer.rb @@ -7,9 +7,14 @@ module Api :desc, :permission, :screenshot, + :starred, :created_at, :updated_at + def starred + object.starred_by_user?(scope[:current_user]) + end + def self.embeddable { user: {}, diff --git a/config/routes.rb b/config/routes.rb index 76158105..05fe5845 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -65,7 +65,10 @@ Metamaps::Application.routes.draw 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 :maps, only: [:index, :create, :show, :update, :destroy] do + post :stars, to: 'stars#create', on: :member + delete :stars, to: 'stars#destroy', on: :member + end resources :synapses, only: [:index, :create, :show, :update, :destroy] resources :tokens, only: [:create, :destroy] do get :my_tokens, on: :collection diff --git a/doc/api/api.raml b/doc/api/api.raml index 50c2c992..6ffa29f1 100644 --- a/doc/api/api.raml +++ b/doc/api/api.raml @@ -1,7 +1,7 @@ #%RAML 1.0 --- title: Metamaps -version: v2 +version: v2.0 baseUri: https://metamaps.cc/api/v2 mediaType: application/json diff --git a/doc/api/apis/maps.raml b/doc/api/apis/maps.raml index b742adce..434dcc67 100644 --- a/doc/api/apis/maps.raml +++ b/doc/api/apis/maps.raml @@ -94,3 +94,15 @@ post: responses: 204: description: No content + /stars: + post: + responses: + 201: + description: Created + body: + application/json: + example: !include ../examples/map.json + delete: + responses: + 204: + description: No content diff --git a/doc/api/examples/map.json b/doc/api/examples/map.json index fe3796ca..20e63204 100644 --- a/doc/api/examples/map.json +++ b/doc/api/examples/map.json @@ -5,6 +5,7 @@ "desc": "Example map for the API", "permission": "commons", "screenshot": "https://s3.amazonaws.com/metamaps-assets/site/missing-map.png", + "starred": false, "created_at": "2016-03-26T08:02:05.379Z", "updated_at": "2016-03-27T07:20:18.047Z", "topic_ids": [ diff --git a/doc/api/examples/maps.json b/doc/api/examples/maps.json index 8b963990..501c0325 100644 --- a/doc/api/examples/maps.json +++ b/doc/api/examples/maps.json @@ -6,6 +6,7 @@ "desc": "Example map for the API", "permission": "commons", "screenshot": "https://s3.amazonaws.com/metamaps-assets/site/missing-map.png", + "starred": false, "created_at": "2016-03-26T08:02:05.379Z", "updated_at": "2016-03-27T07:20:18.047Z", "topic_ids": [ diff --git a/doc/api/schemas/_map.json b/doc/api/schemas/_map.json index 469b4dbe..1234122d 100644 --- a/doc/api/schemas/_map.json +++ b/doc/api/schemas/_map.json @@ -18,6 +18,9 @@ "format": "uri", "type": "string" }, + "starred": { + "type": "boolean" + }, "created_at": { "$ref": "_datetimestamp.json" }, @@ -61,6 +64,7 @@ "desc", "permission", "screenshot", + "starred", "created_at", "updated_at" ] diff --git a/doc/api/schemas/_metacode.json b/doc/api/schemas/_metacode.json index cc6b4f76..2001be8e 100644 --- a/doc/api/schemas/_metacode.json +++ b/doc/api/schemas/_metacode.json @@ -12,6 +12,7 @@ "type": "string" }, "icon": { + "format": "uri", "type": "string" } }, diff --git a/doc/api/schemas/_user.json b/doc/api/schemas/_user.json index e5805251..ee2ef14f 100644 --- a/doc/api/schemas/_user.json +++ b/doc/api/schemas/_user.json @@ -9,6 +9,7 @@ "type": "string" }, "avatar": { + "format": "uri", "type": "string" }, "generation": { diff --git a/spec/api/v2/mappings_api_spec.rb b/spec/api/v2/mappings_api_spec.rb index 4d802865..6f225c6a 100644 --- a/spec/api/v2/mappings_api_spec.rb +++ b/spec/api/v2/mappings_api_spec.rb @@ -16,7 +16,8 @@ RSpec.describe 'mappings API', type: :request do end it 'GET /api/v2/mappings/:id' do - get "/api/v2/mappings/#{mapping.id}" + get "/api/v2/mappings/#{mapping.id}", params: { access_token: token } + expect(response).to have_http_status(:success) expect(response).to match_json_schema(:mapping) diff --git a/spec/api/v2/maps_api_spec.rb b/spec/api/v2/maps_api_spec.rb index 77cbc24b..abed255d 100644 --- a/spec/api/v2/maps_api_spec.rb +++ b/spec/api/v2/maps_api_spec.rb @@ -16,7 +16,7 @@ RSpec.describe 'maps API', type: :request do end it 'GET /api/v2/maps/:id' do - get "/api/v2/maps/#{map.id}" + get "/api/v2/maps/#{map.id}", params: { access_token: token } expect(response).to have_http_status(:success) expect(response).to match_json_schema(:map) @@ -45,6 +45,23 @@ RSpec.describe 'maps API', type: :request do expect(Map.count).to eq 0 end + it 'POST /api/v2/maps/:id/stars' do + post "/api/v2/maps/#{map.id}/stars", params: { access_token: token } + expect(response).to have_http_status(:success) + expect(response).to match_json_schema(:map) + expect(user.stars.count).to eq 1 + expect(map.stars.count).to eq 1 + end + + it 'DELETE /api/v2/maps/:id/stars' do + create(:star, map: map, user: user) + delete "/api/v2/maps/#{map.id}/stars", params: { access_token: token } + + expect(response).to have_http_status(:no_content) + expect(user.stars.count).to eq 0 + expect(map.stars.count).to eq 0 + end + context 'RAML example' do let(:resource) { get_json_example(:map) } let(:collection) { get_json_example(:maps) } diff --git a/spec/api/v2/topics_api_spec.rb b/spec/api/v2/topics_api_spec.rb index 9811071d..3f781df9 100644 --- a/spec/api/v2/topics_api_spec.rb +++ b/spec/api/v2/topics_api_spec.rb @@ -16,7 +16,8 @@ RSpec.describe 'topics API', type: :request do end it 'GET /api/v2/topics/:id' do - get "/api/v2/topics/#{topic.id}" + get "/api/v2/topics/#{topic.id}", params: { access_token: token } + expect(response).to have_http_status(:success) expect(response).to match_json_schema(:topic) diff --git a/spec/factories/stars.rb b/spec/factories/stars.rb new file mode 100644 index 00000000..60b10cf1 --- /dev/null +++ b/spec/factories/stars.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true +FactoryGirl.define do + factory :star do + end +end