diff --git a/src/internal/movies/handlers.go b/src/internal/movies/handlers.go index 9416a4c..982fb88 100644 --- a/src/internal/movies/handlers.go +++ b/src/internal/movies/handlers.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" + "github.com/Sirupsen/logrus" "github.com/gorilla/mux" "github.com/odwrtw/papi" polochon "github.com/odwrtw/polochon/lib" @@ -191,3 +192,38 @@ func SearchMovie(env *web.Env, w http.ResponseWriter, r *http.Request) error { return env.RenderJSON(w, movieList) } + +// DeleteHandler deletes the movie from polochon +func DeleteHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error { + vars := mux.Vars(r) + id := vars["id"] + + log := env.Log.WithFields(logrus.Fields{ + "imdb_id": id, + "function": "movies.DeleteHandler", + }) + log.Debugf("deleting movie") + + v := auth.GetCurrentUser(r, env.Log) + user, ok := v.(*users.User) + if !ok { + return fmt.Errorf("invalid user type") + } + + var polochonConfig config.UserPolochon + err := user.GetConfig("polochon", &polochonConfig) + if err != nil { + return err + } + + client, err := papi.New(polochonConfig.URL) + if err != nil { + return err + } + + if polochonConfig.Token != "" { + client.SetToken(polochonConfig.Token) + } + + return client.Delete(&papi.Movie{ImdbID: id}) +} diff --git a/src/main.go b/src/main.go index cbea35a..f0d8982 100644 --- a/src/main.go +++ b/src/main.go @@ -78,6 +78,7 @@ func main() { env.Handle("/movies/polochon", movies.FromPolochon).WithRole(users.UserRole).Methods("GET") env.Handle("/movies/{id:tt[0-9]+}/get_details", movies.GetDetailsHandler).WithRole(users.UserRole).Methods("GET") + env.Handle("/movies/{id:tt[0-9]+}", movies.DeleteHandler).WithRole(users.AdminRole).Methods("DELETE") env.Handle("/movies/explore", extmedias.Explore).WithRole(users.UserRole).Methods("GET") env.Handle("/movies/refresh", extmedias.Refresh).WithRole(users.UserRole).Methods("POST") env.Handle("/movies/search", movies.SearchMovie).WithRole(users.UserRole).Methods("POST") diff --git a/src/public/js/actions/actionCreators.js b/src/public/js/actions/actionCreators.js index 32354e6..c630e2a 100644 --- a/src/public/js/actions/actionCreators.js +++ b/src/public/js/actions/actionCreators.js @@ -61,7 +61,9 @@ export function updateUser(config) { return request( 'USER_UPDATE', configureAxios().post('/users/edit', config), - "User updated", + [ + addAlertOk("User updated"), + ], ) } @@ -90,6 +92,13 @@ export function selectMovie(imdbId) { } } +export function deleteMovieFromStore(imdbId) { + return { + type: 'DELETE_MOVIE', + imdbId + } +} + export function searchMovies(search) { return request( 'SEARCH_MOVIES', @@ -104,6 +113,17 @@ export function getMovieDetails(imdbId) { ) } +export function deleteMovie(imdbId) { + return request( + 'MOVIE_DELETE', + configureAxios().delete(`/movies/${imdbId}`), + [ + addAlertOk("Movie deleted"), + deleteMovieFromStore(imdbId), + ], + ) +} + export function fetchMovies(url) { return request( 'MOVIE_LIST_FETCH', @@ -153,6 +173,8 @@ export function addTorrent(url) { configureAxios().post('/torrents', { url: url, }), - "Torrent added", + [ + addAlertOk("Torrent added"), + ], ) } diff --git a/src/public/js/components/movies/actions.js b/src/public/js/components/movies/actions.js new file mode 100644 index 0000000..e78e634 --- /dev/null +++ b/src/public/js/components/movies/actions.js @@ -0,0 +1,72 @@ +import React from 'react' + +import { DropdownButton, MenuItem } from 'react-bootstrap' + +export default function ActionsButton(props) { + return ( + + + {(props.isUserAdmin && props.hasMovie) && + + } + + ); +} + +class RefreshButton extends React.Component { + constructor(props) { + super(props); + this.handleClick = this.handleClick.bind(this); + } + handleClick(e) { + e.preventDefault(); + if (this.props.fetching) { + return + } + this.props.getDetails(this.props.movieId); + } + render() { + return ( + + {this.props.fetching || + + Refresh + + } + {this.props.fetching && + + Refreshing + + } + + ); + } +} + +class DeleteButton extends React.Component { + constructor(props) { + super(props); + this.handleClick = this.handleClick.bind(this); + } + handleClick(e) { + e.preventDefault(); + this.props.deleteMovie(this.props.movieId); + } + render() { + return ( + + + Delete + + + ); + } +} diff --git a/src/public/js/components/movies/list.js b/src/public/js/components/movies/list.js index a2aa0f2..37866b7 100644 --- a/src/public/js/components/movies/list.js +++ b/src/public/js/components/movies/list.js @@ -1,57 +1,43 @@ import React from 'react' import TorrentsButton from './torrents' +import ActionsButton from './actions' import ListPosters from '../list/posters' import ListDetails from '../list/details' import Loader from '../loader/loader' -class MovieButtons extends React.Component { - constructor(props) { - super(props); - this.handleClick = this.handleClick.bind(this); - } - handleClick(e) { - e.preventDefault(); - if (this.props.fetching) { - return - } - this.props.getMovieDetails(this.props.movie.imdb_id); - } - render() { - const imdb_link = `http://www.imdb.com/title/${this.props.movie.imdb_id}`; - return ( -
- - {this.props.fetching || - - Refresh - - } - {this.props.fetching && - - Refreshing - - } +function MovieButtons(props) { + const imdb_link = `http://www.imdb.com/title/${props.movie.imdb_id}`; + const hasMovie = (props.movie.polochon_url !== "") + return ( +
+ {hasMovie && + + Download - {this.props.movie.polochon_url !== "" && - - Download - - } + } - {this.props.movie.torrents && - - } + {props.movie.torrents && + + } - - IMDB - -
- ); - } + + + + IMDB + +
+ ); } export default class MovieList extends React.Component { @@ -109,6 +95,8 @@ export default class MovieList extends React.Component { fetching={this.props.movieStore.fetchingDetails} getMovieDetails={this.props.getMovieDetails} addTorrent={this.props.addTorrent} + deleteMovie={this.props.deleteMovie} + isUserAdmin={this.props.userStore.isAdmin} /> } diff --git a/src/public/js/components/navbar.js b/src/public/js/components/navbar.js index a21ec94..3f9bbab 100644 --- a/src/public/js/components/navbar.js +++ b/src/public/js/components/navbar.js @@ -6,102 +6,128 @@ import { Nav, Navbar, NavItem, NavDropdown, MenuItem } from 'react-bootstrap' import { LinkContainer } from 'react-router-bootstrap' import { Control, Form } from 'react-redux-form'; -export default class NavBar extends React.Component { +export default function NavBar(props) { + return ( +
+ + + + Canapé + + + + + + + + + + + +
+ ); +} + +class Search extends React.Component { constructor(props) { super(props); - this.handleMovieSearch = this.handleMovieSearch.bind(this); - this.handleShowSearch = this.handleShowSearch.bind(this); + this.handleSearch = this.handleSearch.bind(this); } - handleMovieSearch() { - this.props.router.push(`/movies/search/${encodeURI(this.props.movieStore.search)}`) - } - handleShowSearch() { - this.props.router.push(`/shows/search/${encodeURI(this.props.showStore.search)}`) + handleSearch() { + this.props.router.push(`${this.props.path}/${encodeURI(this.props.search)}`) } render() { - const username = this.props.userStore.username; - const isLoggedIn = username !== "" ? true : false; const location = this.props.router.getCurrentLocation().pathname; - let displayMovieSearch = false; - let displayShowSearch = false; - if (isLoggedIn && location.indexOf("movies") > -1) + if (location.indexOf(this.props.pathMatch) === -1) { - displayMovieSearch = true; + return null; } - if (isLoggedIn && location.indexOf("shows") > -1) - { - displayShowSearch = true; - } - return ( -
- - - - Canapé - - - - - - - {displayMovieSearch && - -
this.handleMovieSearch()}> - - -
- } - {displayShowSearch && - -
this.handleShowSearch()}> - - -
- } -
-
-
+ + return( + +
+ + +
+ ); + } +} + +function MoviesDropdown(props) { + return( + + ); +} + +function ShowsDropdown(props) { + return( + + ); +} + +function UserDropdown(props) { + if (props.username !== "") { + return ( + + ); + } else { + return( + ); } } diff --git a/src/public/js/reducers/movies.js b/src/public/js/reducers/movies.js index a05da07..83f9bd1 100644 --- a/src/public/js/reducers/movies.js +++ b/src/public/js/reducers/movies.js @@ -46,6 +46,11 @@ export default function movieStore(state = defaultState, action) { movies: movies, fetchingDetails: false, }) + case 'DELETE_MOVIE': + return Object.assign({}, state, { + movies: state.movies.filter((e) => (e.imdb_id !== action.imdbId)), + fetchingDetails: false, + }) case 'SELECT_MOVIE': // Don't select the movie if we're fetching another movie's details if (state.fetchingDetails) { diff --git a/src/public/js/requests.js b/src/public/js/requests.js index cf64b98..25424c8 100644 --- a/src/public/js/requests.js +++ b/src/public/js/requests.js @@ -16,7 +16,7 @@ export function configureAxios(headers = {}) { // This function takes en event prefix to dispatch evens during the life of the // request, it also take a promise (axios request) -export function request(eventPrefix, promise, successMessage = null) { +export function request(eventPrefix, promise, callbackEvents = null) { // Events const pending = `${eventPrefix}_PENDING`; const fulfilled = `${eventPrefix}_FULFILLED`; @@ -40,13 +40,10 @@ export function request(eventPrefix, promise, successMessage = null) { type: fulfilled, payload: response.data, }) - if (successMessage) { - dispatch({ - type: 'ADD_ALERT_OK', - payload: { - message: successMessage, - }, - }) + if (callbackEvents) { + for (let event of callbackEvents) { + dispatch(event); + } } }) .catch(error => {