diff --git a/app/assets/stylesheets/components/show_box_content.scss b/app/assets/stylesheets/components/show_box_content.scss index 820bbb4..22fbb6f 100644 --- a/app/assets/stylesheets/components/show_box_content.scss +++ b/app/assets/stylesheets/components/show_box_content.scss @@ -33,6 +33,19 @@ font-family: Circular,Helvetica,Arial,sans-serif; } +.show-follow { + display: flex; + justify-content: center; +} + +.follow-button { + padding-top: 10px; + color: lightgray; + font-weight: 100; + font-size: 14px; + font-family: Circular,Helvetica,Arial,sans-serif; +} + .loading { width: 60px; margin: auto; diff --git a/app/controllers/api/follows_controller.rb b/app/controllers/api/follows_controller.rb index 6a4655e..6c6f3e7 100644 --- a/app/controllers/api/follows_controller.rb +++ b/app/controllers/api/follows_controller.rb @@ -1,2 +1,33 @@ class Api::FollowsController < ApplicationController + def create + + @follow = Follow.new(follow_params) + if @follow.save + render 'api/follows/show' + else + render json: ["Could not process request"], status: 401 + end + end + + def destroy + + @follow = Follow.find_by( + user_id: follow_params[:user_id], + followable_id: follow_params[:followable_id], + followable_type: follow_params[:followable_type], + ) + if @follow + f = @follow.dup + @follow.destroy + render json: f + else + render json: ["Connection could not be found"], status: 404 + end + end + + private + + def follow_params + params.require(:follow).permit(:user_id, :followable_id, :followable_type) + end end diff --git a/app/models/artist.rb b/app/models/artist.rb index 04c84b9..3b7e4a7 100644 --- a/app/models/artist.rb +++ b/app/models/artist.rb @@ -24,4 +24,7 @@ class Artist < ApplicationRecord has_many :albums, through: :songs, source: :album + + has_many :follows, as: :followable, dependent: :destroy + has_many :followers, through: :follows, source: :user end diff --git a/app/models/follow.rb b/app/models/follow.rb index 7ba32c5..800f0c1 100644 --- a/app/models/follow.rb +++ b/app/models/follow.rb @@ -7,8 +7,13 @@ # followeable_type :string # created_at :datetime not null # updated_at :datetime not null +# user_id :integer # class Follow < ApplicationRecord - belongs_to :followeable_id + validates :user_id, :followable_id, :followable_type, presence: true + validates :user_id, uniqueness: { :scope => [:followable_type, :followable_id] } + + belongs_to :user + belongs_to :followable, polymorphic: true end diff --git a/app/models/playlist.rb b/app/models/playlist.rb index a6f9cc8..6ed92a5 100644 --- a/app/models/playlist.rb +++ b/app/models/playlist.rb @@ -30,4 +30,7 @@ class Playlist < ApplicationRecord has_one_attached :photo has_one_attached :track + + has_many :follows, as: :followable, dependent: :destroy + has_many :followers, through: :follows, source: :user end diff --git a/app/models/user.rb b/app/models/user.rb index 1d2af85..5c784f7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -38,6 +38,10 @@ class User < ApplicationRecord through: :songs, source: :artists + has_many :follows + has_many :followed_artists, through: :follows, source: :followable, source_type: 'Artist' + has_many :followed_playlists, through: :follows, source: :followable, source_type: 'Playlist' + def password=(password) @password = password self.password_digest = BCrypt::Password.create(password) diff --git a/app/views/api/follows/show.json.jbuilder b/app/views/api/follows/show.json.jbuilder new file mode 100644 index 0000000..ded9ece --- /dev/null +++ b/app/views/api/follows/show.json.jbuilder @@ -0,0 +1 @@ +json.extract! @follow, :id, :user_id, :followable_id, :followable_type diff --git a/app/views/api/users/_user.json.jbuilder b/app/views/api/users/_user.json.jbuilder index 99a82f5..4a5f648 100644 --- a/app/views/api/users/_user.json.jbuilder +++ b/app/views/api/users/_user.json.jbuilder @@ -1 +1 @@ -json.extract! user, :id, :username +json.extract! user, :id, :username, :followed_artist_ids, :followed_playlist_ids diff --git a/app/views/api/users/show.json.jbuilder b/app/views/api/users/show.json.jbuilder index 6474d4f..155147a 100644 --- a/app/views/api/users/show.json.jbuilder +++ b/app/views/api/users/show.json.jbuilder @@ -1,3 +1,3 @@ -json.extract! @user, :id, :username, :song_ids, :album_ids +json.extract! @user, :id, :username, :song_ids, :album_ids, :followed_artist_ids, :followed_playlist_ids # json.artist_ids @artists.pluck(:id) diff --git a/db/migrate/20181229235133_add_userid_to_follows.rb b/db/migrate/20181229235133_add_userid_to_follows.rb new file mode 100644 index 0000000..dceab9a --- /dev/null +++ b/db/migrate/20181229235133_add_userid_to_follows.rb @@ -0,0 +1,5 @@ +class AddUseridToFollows < ActiveRecord::Migration[5.2] + def change + add_column :follows, :user_id, :integer + end +end diff --git a/db/migrate/20181229235944_change_follows_column_names.rb b/db/migrate/20181229235944_change_follows_column_names.rb new file mode 100644 index 0000000..1e762e2 --- /dev/null +++ b/db/migrate/20181229235944_change_follows_column_names.rb @@ -0,0 +1,6 @@ +class ChangeFollowsColumnNames < ActiveRecord::Migration[5.2] + def change + rename_column :follows, :followeable_id, :followable_id + rename_column :follows, :followeable_type, :followable_type + end +end diff --git a/db/schema.rb b/db/schema.rb index 9312314..52dae8c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2018_11_18_235623) do +ActiveRecord::Schema.define(version: 2018_12_29_235944) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -50,10 +50,11 @@ end create_table "follows", force: :cascade do |t| - t.integer "followeable_id" - t.string "followeable_type" + t.integer "followable_id" + t.string "followable_type" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "user_id" end create_table "playlists", force: :cascade do |t| diff --git a/frontend/actions/follow_actions.js b/frontend/actions/follow_actions.js new file mode 100644 index 0000000..8941420 --- /dev/null +++ b/frontend/actions/follow_actions.js @@ -0,0 +1,30 @@ +import * as FollowAPIUtil from '../util/follow_api_util'; + +export const RECEIVE_FOLLOW = 'RECEIVE_FOLLOW'; +export const REMOVE_FOLLOW = 'REMOVE_FOLLOW'; + +const receiveFollow = follow => { + return { + type: RECEIVE_FOLLOW, + follow + }; +}; + +const removeFollow = follow => { + return { + type: REMOVE_FOLLOW, + follow + }; +}; + +export const createFollow = follow => { + return dispatch => FollowAPIUtil.createFollow(follow).then( + res => dispatch(receiveFollow(res)) + ); +}; + +export const deleteFollow = follow => { + return dispatch => FollowAPIUtil.deleteFollow(follow).then( + res => dispatch(removeFollow(res)) + ); +}; diff --git a/frontend/actions/search_actions.js b/frontend/actions/search_actions.js index af2f3af..47431c8 100644 --- a/frontend/actions/search_actions.js +++ b/frontend/actions/search_actions.js @@ -4,7 +4,7 @@ export const RECEIVE_ALL_ALBUMS = 'RECEIVE_ALL_ALBUMS'; export const RECEIVE_ALBUM = 'RECEIVE_ALBUM'; export const requestAllAlbums = (searchQuery) => { - debugger + return (dispatch) => { return SearchApiUtil.fetchAllAlbums(searchQuery) .then(null, diff --git a/frontend/components/main/header/album_index_container.js b/frontend/components/main/header/album_index_container.js index 5a9fbe3..b5d1d51 100644 --- a/frontend/components/main/header/album_index_container.js +++ b/frontend/components/main/header/album_index_container.js @@ -6,7 +6,7 @@ import { requestAllAlbums } from '../../../actions/search_actions'; import { selectRandomAlbums } from '../../../reducers/selectors'; const mapStateToProps = (state, ownProps) => { - debugger + return { path: "album", navpath: ownProps.navpath, diff --git a/frontend/components/main/header/artist_show_container.js b/frontend/components/main/header/artist_show_container.js index fd15730..afb634f 100644 --- a/frontend/components/main/header/artist_show_container.js +++ b/frontend/components/main/header/artist_show_container.js @@ -4,24 +4,31 @@ import { connect } from 'react-redux'; import GridShow from './grid_show'; import { logout } from '../../../actions/session_actions'; import { selectArtistSongs } from '../../../reducers/selectors'; +import { createFollow, deleteFollow } from '../../../actions/follow_actions'; const mapStateToProps = (state, ownProps) => { - debugger + // const artist = Object.values(state.entities.remoteArtists)[ownProps.match.params.artistId] || ownProps.artists || [], const artist = state.entities.artists[ownProps.match.params.artistId] || { name: "", song_ids: [], photoUrl: "" }; const songs = selectArtistSongs(state, artist); + + // const currentUser = state.entities.users[state.session.currentUserId]; + const currentUser = state.session.currentUser; return { artist, artistId: ownProps.match.params.artistId, - songs + songs, + currentUser }; }; const mapDipatchToProps = (dispatch) => { return { fetchArtist: (id) => dispatch(fetchArtist(id)), - logout: () => dispatch(logout()) + logout: () => dispatch(logout()), + createFollow: (follow) => dispatch(createFollow(follow)), + deleteFollow: (follow) => dispatch(deleteFollow(follow)) }; }; diff --git a/frontend/components/main/header/grid_index.jsx b/frontend/components/main/header/grid_index.jsx index ba362db..167ca6b 100644 --- a/frontend/components/main/header/grid_index.jsx +++ b/frontend/components/main/header/grid_index.jsx @@ -11,7 +11,7 @@ class GridIndex extends React.Component { } componentDidMount(){ - debugger + this.fetchElements( {search_term: this.props.searchTerm} ); @@ -26,7 +26,7 @@ class GridIndex extends React.Component { } render(){ - debugger + const {playlists, artists, albums, navpath, path} = this.props; let property = "title"; let gridElements = []; diff --git a/frontend/components/main/header/grid_show.jsx b/frontend/components/main/header/grid_show.jsx index a3c3fe1..e4d116b 100644 --- a/frontend/components/main/header/grid_show.jsx +++ b/frontend/components/main/header/grid_show.jsx @@ -14,6 +14,8 @@ class GridShow extends React.Component { this.elementId = this.props.playlistId || this.props.albumId || this.props.artistId; this.fetchElement = this.fetchElement.bind(this); this.timeout = this.timeout.bind(this); + this.handleFollow = this.handleFollow.bind(this); + } timeout(){ @@ -25,6 +27,18 @@ class GridShow extends React.Component { .then( this.timeout ); } + handleFollow(e) { + + const following = this.props.currentUser.followed_artist_ids.includes(this.props.artist.id); + const follow = { + user_id: this.props.currentUser.id, + followable_id: this.props.artist.id, + followable_type: 'Artist' + }; + + following ? this.props.deleteFollow(follow) : this.props.createFollow(follow); + } + render () { function isDefined(song){ @@ -66,6 +80,16 @@ class GridShow extends React.Component { element.artworkUrl100 = element.artworkUrl100.replace('100x100', '600x600'); } + let follows = ""; + if (this.props.artist) { + follows = ( + + ); + } + return (
{element.title || element.name || element.collectionName}
{element.author || element.artists || element.artistName}