Commit c9f77a31 authored by Florian Hübsch's avatar Florian Hübsch
Browse files

Update Collection API, add treeview for shared collections. In

more detail:

- add authentication to API
- fetch collections in API by current_user.id
- fetch roots and shared_roots separately
parent 641f8081
......@@ -16,20 +16,24 @@
# Available Seeds
A user is seeded with email `test@ninjaconcept.com` and password `ninjaconcept`.
Currently 3 users are seeded with respective email `test@ninjaconcept.com`, `hattori@ninjaconcept.com`, `momochi@ninjaconcept.com`, and password `ninjaconcept` (for all 3 the same).
# API (v1)
## Collections
* Get serialized collection roots
* Get serialized, unshared collection roots for current user
`/api/v1/collections/roots`
* Get serialized, shared collection roots for current user
`/api/v1/collections/shared_roots`
* Get serialized samples by collection id
`/api/v1/collections/:collection_id/samples`
* Get serialized sample by id
`/api/v1/samples/:id`
......@@ -4,6 +4,26 @@ class API < Grape::API
format :json
formatter :json, Grape::Formatter::ActiveModelSerializers
# TODO needs to be tested,
# source: http://funonrails.com/2014/03/api-authentication-using-devise-token/
helpers do
def warden
env['warden']
end
def current_user
@current_user = warden.user
end
def authenticate!
error!('401 Unauthorized', 401) unless current_user
end
end
before do
authenticate!
end
mount Chemotion::CollectionAPI
mount Chemotion::SampleAPI
end
module Chemotion
class CollectionAPI < Grape::API
resource :collections do
desc "Return all serialized collection roots"
desc "Return all unshared serialized collection roots"
get :roots do
Collection.roots
current_user.collections.unshared.roots
end
desc "Return all shared serialized collection roots"
get :shared_roots do
current_user.collections.shared.roots
end
# TODO add authorization/authentication, e.g. is current_user allowed
# to fetch this samples?
desc "Return serialized samples for given collection id"
params do
requires :id, type: Integer, desc: "Collection id"
......
......@@ -14,7 +14,7 @@ export default class CollectionTree extends React.Component {
componentDidMount() {
CollectionStore.listen(this.onChange.bind(this));
CollectionActions.fetchCollections();
CollectionActions.fetchUnsharedCollectionRoots();
}
componentWillUnmount() {
......@@ -39,8 +39,14 @@ export default class CollectionTree extends React.Component {
render() {
return (
<div className="tree-wrapper">
{this.subtrees()}
<div>
<div className="tree-wrapper">
{this.subtrees()}
</div>
Shared
<div className="tree-wrapper">
{this.subtrees()}
</div>
</div>
)
}
......
......@@ -2,10 +2,17 @@ import alt from '../alt';
import CollectionsFetcher from '../fetchers/CollectionsFetcher';
class CollectionActions {
// TODO #1 unify naming: fetchCollections or fetchCollectionRoots?
// or do we need both?
// TODO #2...centralized error handling maybe ErrorActions?
fetchCollections() {
fetchUnsharedCollectionRoots() {
CollectionsFetcher.fetchRoots()
.then((roots) => {
this.dispatch(roots);
}).catch((errorMessage) => {
console.log(errorMessage);
});
}
fetchSharedCollectionRoots() {
CollectionsFetcher.fetchRoots()
.then((roots) => {
this.dispatch(roots);
......
......@@ -2,7 +2,9 @@ import 'whatwg-fetch';
export default class CollectionsFetcher {
static fetchRoots() {
let promise = fetch('/api/v1/collections/roots.json')
let promise = fetch('/api/v1/collections/roots.json', {
credentials: 'same-origin'
})
.then((response) => {
return response.json()
}).then((json) => {
......
......@@ -3,7 +3,9 @@ import 'whatwg-fetch';
// TODO: SamplesFetcher also updates Samples and so on...naming?
export default class SamplesFetcher {
static fetchById(id) {
let promise = fetch('/api/v1/samples/' + id + '.json')
let promise = fetch('/api/v1/samples/' + id + '.json', {
credentials: 'same-origin'
})
.then((response) => {
return response.json()
}).then((json) => {
......@@ -16,7 +18,9 @@ export default class SamplesFetcher {
}
static fetchByCollectionId(id) {
let promise = fetch('/api/v1/collections/' + id + '/samples.json')
let promise = fetch('/api/v1/collections/' + id + '/samples.json', {
credentials: 'same-origin'
})
.then((response) => {
return response.json()
}).then((json) => {
......@@ -30,6 +34,7 @@ export default class SamplesFetcher {
static update(paramObj) {
let promise = fetch('/api/v1/samples/' + paramObj.id, {
credentials: 'same-origin',
method: 'put',
headers: {
'Accept': 'application/json',
......
......@@ -8,7 +8,7 @@ class CollectionStore {
};
this.bindListeners({
handleFetchCollections: CollectionActions.fetchCollections
handleFetchCollections: CollectionActions.fetchUnsharedCollectionRoots
})
}
......
......@@ -5,6 +5,7 @@ class Collection < ActiveRecord::Base
has_many :collections_samples
has_many :samples, through: :collections_samples
scope :unshared, -> { where(is_shared: false) }
scope :shared, -> { where(is_shared: true) }
def is_all_collection?
......
......@@ -3,4 +3,6 @@ class User < ActiveRecord::Base
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :collections
end
# create initial test user
u = User.create!(email: 'test@ninjaconcept.com', password: 'ninjaconcept', password_confirmation: 'ninjaconcept')
hattori = User.create!(email: 'hattori@ninjaconcept.com', password: 'ninjaconcept', password_confirmation: 'ninjaconcept')
momochi = User.create!(email: 'momochi@ninjaconcept.com', password: 'ninjaconcept', password_confirmation: 'ninjaconcept')
# create some collections
all_collection = Collection.create!(label: 'All', user_id: u.id)
......@@ -15,6 +17,10 @@ reaction_4 = Reaction.create(name: 'Reaction 4')
reaction_5 = Reaction.create(name: 'Reaction 5')
reaction_6 = Reaction.create(name: 'Reaction 6')
# create some shared collections
shared_collection_1 = Collection.create!(is_shared: true, label: 'My project with Hattori', user_id: hattori.id)
shared_collection_2 = Collection.create!(is_shared: true, label: 'My project with Momochi', user_id: momochi.id)
# create some samples
sample_1 = Sample.create!(name: 'Sample 1')
sample_2 = Sample.create!(name: 'Sample 2')
......@@ -46,6 +52,10 @@ CollectionsSample.create!(sample: sample_1, collection: grand_child)
CollectionsSample.create!(sample: sample_7, collection: grand_child)
CollectionsSample.create!(sample: sample_8, collection: grand_child)
CollectionsSample.create!(sample: sample_1, collection: shared_collection_1)
CollectionsSample.create!(sample: sample_2, collection: shared_collection_2)
CollectionsSample.create!(sample: sample_3, collection: shared_collection_2)
# associate samples with reactions
CollectionsReaction.create!(reaction: reaction_1, collection: collection_1)
CollectionsReaction.create!(reaction: reaction_2, collection: collection_1)
......@@ -53,3 +63,4 @@ CollectionsReaction.create!(reaction: reaction_3, collection: collection_1)
CollectionsReaction.create!(reaction: reaction_4, collection: grand_child)
CollectionsReaction.create!(reaction: reaction_5, collection: subcollection_1)
CollectionsReaction.create!(reaction: reaction_6, collection: subcollection_1)
......@@ -10,13 +10,19 @@ RSpec.describe Collection, type: :model do
end
describe 'scopes' do
describe 'shared scope' do
let(:collection_1) { create(:collection, is_shared: false) }
let(:collection_2) { create(:collection, is_shared: true) }
let(:collection_1) { create(:collection, is_shared: false) }
let(:collection_2) { create(:collection, is_shared: true) }
describe 'shared scope' do
it 'returns shared collections' do
expect(Collection.shared).to match_array [collection_2]
end
end
describe 'unshared scope' do
it 'returns unshared collections' do
expect(Collection.unshared).to match_array [collection_1]
end
end
end
end
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment