diff --git a/client/src/components/App.jsx b/client/src/components/App.jsx index f44c550..4c2f39f 100644 --- a/client/src/components/App.jsx +++ b/client/src/components/App.jsx @@ -16,7 +16,6 @@ export class App extends React.Component { - { window.location = "/logout"; return null; diff --git a/client/src/components/BoardPage/EditBoardPage.jsx b/client/src/components/BoardPage/EditBoardPage.jsx index 4c4de9c..89ecc6f 100644 --- a/client/src/components/BoardPage/EditBoardPage.jsx +++ b/client/src/components/BoardPage/EditBoardPage.jsx @@ -2,16 +2,17 @@ import React from 'react'; import { Page } from '../Page'; import { connect } from 'react-redux'; import { selectFlagsIsLoading } from '../../store/selectors/flags'; -import { fetchBoards, saveBoard } from '../../store/actions/boards'; +import { fetchBoards, saveBoard, deleteBoard } from '../../store/actions/boards'; import { fetchProjects } from '../../store/actions/projects'; import uuidv4 from 'uuid/v4'; +import { Loader } from '../Loader'; export class EditBoardPage extends React.Component { state = { edited: false, board: { - id: uuidv4(), + id: "", title: "", description: "", projects: [], @@ -42,6 +43,7 @@ export class EditBoardPage extends React.Component { this.onBoardDescriptionChange = this.onBoardAttrChange.bind(this, 'description'); this.onBoardLaneTitleChange = this.onBoardLaneAttrChange.bind(this, 'title'); this.onBoardLaneIssueLabelChange = this.onBoardLaneAttrChange.bind(this, 'issueLabel'); + this.onDeleteBoardClick = this.onDeleteBoardClick.bind(this); } render() { @@ -50,7 +52,9 @@ export class EditBoardPage extends React.Component { if (isLoading) { return ( -

Loading...

+ + + ) }; @@ -59,11 +63,27 @@ export class EditBoardPage extends React.Component {
- { - board.id ? -

Éditer le tableau

: -

Nouveau tableau

- } +
+
+ { + board.id ? +

Éditer le tableau

: +

Nouveau tableau

+ } +
+
+ { + board.id ? + : + null + } +
+
@@ -366,8 +386,16 @@ export class EditBoardPage extends React.Component { } onSaveBoardClick() { + let { board } = this.state; + board = { ...board }; + if (!board.id) board.id = uuidv4(); + this.props.dispatch(saveBoard({...board})); + this.props.history.push('/'); + } + + onDeleteBoardClick() { const { board } = this.state; - this.props.dispatch(saveBoard(board)); + this.props.dispatch(deleteBoard(board.id)); this.props.history.push('/'); } diff --git a/client/src/components/HomePage/BoardCard.jsx b/client/src/components/HomePage/BoardCard.jsx index 24b016d..461a1bb 100644 --- a/client/src/components/HomePage/BoardCard.jsx +++ b/client/src/components/HomePage/BoardCard.jsx @@ -26,11 +26,6 @@ export class BoardCard extends React.PureComponent { - - - - -
diff --git a/client/src/store/actions/boards.js b/client/src/store/actions/boards.js index 49d4fe1..0eccc97 100644 --- a/client/src/store/actions/boards.js +++ b/client/src/store/actions/boards.js @@ -12,4 +12,12 @@ export const SAVE_BOARD_FAILURE = "SAVE_BOARD_FAILURE"; export function saveBoard(board) { return { type: SAVE_BOARD_REQUEST, board }; +}; + +export const DELETE_BOARD_REQUEST = "DELETE_BOARD_REQUEST"; +export const DELETE_BOARD_SUCCESS = "DELETE_BOARD_SUCCESS"; +export const DELETE_BOARD_FAILURE = "DELETE_BOARD_FAILURE"; + +export function deleteBoard(id) { + return { type: DELETE_BOARD_REQUEST, id }; }; \ No newline at end of file diff --git a/client/src/store/reducers/boards.js b/client/src/store/reducers/boards.js index 7202f28..5ed2752 100644 --- a/client/src/store/reducers/boards.js +++ b/client/src/store/reducers/boards.js @@ -16,12 +16,13 @@ export function boardsReducer(state = defaultState, action) { } function handleSaveBoardSuccess(state, action) { + const { board } = action; return { ...state, byID: { ...state.byID, - [action.board.id.toString()]: { - ...action.board, + [board.id]: { + ...board, } } }; diff --git a/client/src/store/sagas/boards.js b/client/src/store/sagas/boards.js index c2d600d..c6e8fd1 100644 --- a/client/src/store/sagas/boards.js +++ b/client/src/store/sagas/boards.js @@ -1,5 +1,5 @@ import { put, call } from 'redux-saga/effects'; -import { FETCH_BOARDS_SUCCESS, SAVE_BOARD_SUCCESS, SAVE_BOARD_FAILURE, FETCH_BOARDS_FAILURE } from '../actions/boards'; +import { FETCH_BOARDS_SUCCESS, SAVE_BOARD_SUCCESS, SAVE_BOARD_FAILURE, FETCH_BOARDS_FAILURE, DELETE_BOARD_FAILURE, DELETE_BOARD_SUCCESS } from '../actions/boards'; import { api } from '../../util/api'; export function* fetchBoardsSaga() { @@ -27,3 +27,17 @@ export function* saveBoardSaga(action) { yield put({ type: SAVE_BOARD_SUCCESS, board }); } + + +export function* deleteBoardSaga(action) { + let { id } = action; + + try { + board = yield call(api.deleteBoard, id) + } catch(error) { + yield put({ type: DELETE_BOARD_FAILURE, error }); + return + } + + yield put({ type: DELETE_BOARD_SUCCESS, id }); +} \ No newline at end of file diff --git a/client/src/store/sagas/root.js b/client/src/store/sagas/root.js index e73231a..8c6b45a 100644 --- a/client/src/store/sagas/root.js +++ b/client/src/store/sagas/root.js @@ -1,7 +1,7 @@ import { all, takeEvery, takeLatest } from 'redux-saga/effects'; import { failuresSaga } from './failure'; -import { FETCH_BOARDS_REQUEST, SAVE_BOARD_REQUEST } from '../actions/boards'; -import { fetchBoardsSaga, saveBoardSaga } from './boards'; +import { FETCH_BOARDS_REQUEST, SAVE_BOARD_REQUEST, DELETE_BOARD_REQUEST } from '../actions/boards'; +import { fetchBoardsSaga, saveBoardSaga, deleteBoardSaga } from './boards'; import { FETCH_ISSUES_REQUEST, ADD_LABEL_REQUEST, REMOVE_LABEL_REQUEST, CREATE_ISSUE_REQUEST, CREATE_ISSUE_SUCCESS } from '../actions/issues'; import { fetchIssuesSaga, addLabelSaga, removeLabelSaga, createIssueSaga } from './issues'; import { FETCH_PROJECTS_REQUEST } from '../actions/projects'; @@ -17,6 +17,7 @@ export function* rootSaga() { takeLatest(FETCH_BOARDS_REQUEST, fetchBoardsSaga), takeLatest(BUILD_KANBOARD_REQUEST, buildKanboardSaga), takeLatest(SAVE_BOARD_REQUEST, saveBoardSaga), + takeLatest(DELETE_BOARD_REQUEST, deleteBoardSaga), takeLatest(FETCH_ISSUES_REQUEST, fetchIssuesSaga), takeLatest(FETCH_PROJECTS_REQUEST, fetchProjectsSaga), takeEvery(MOVE_CARD, moveCardSaga), diff --git a/client/src/util/api.js b/client/src/util/api.js index ec664f0..437c755 100644 --- a/client/src/util/api.js +++ b/client/src/util/api.js @@ -13,6 +13,12 @@ export class APIClient { ; } + deleteBoard(id) { + return fetch(`/api/boards/${id}`, { + method: 'DELETE' + }); + } + fetchBoards() { return fetch(`/api/boards`) .then(res => res.json()) diff --git a/internal/repository/repository.go b/internal/repository/repository.go index 105bc2c..28bdf23 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -1,5 +1,13 @@ package repository +import ( + "github.com/pkg/errors" +) + +var ( + ErrNotFound = errors.New("not found") +) + type Repository struct { boards BoardRepository } diff --git a/internal/repository/storm/board.go b/internal/repository/storm/board.go index 922bc5a..8d04e9f 100644 --- a/internal/repository/storm/board.go +++ b/internal/repository/storm/board.go @@ -61,6 +61,18 @@ func (r *BoardRepository) Save(board *repository.Board) error { } func (r *BoardRepository) Delete(id repository.BoardID) error { + b := &boardItem{ + ID: string(id), + } + + if err := r.db.DeleteStruct(b); err != nil { + if err == storm.ErrNotFound { + return repository.ErrNotFound + } + + return errors.Wrapf(err, "could not delete board '%s'", id) + } + return nil } diff --git a/internal/route/board.go b/internal/route/board.go index 39e12a3..27aabfc 100644 --- a/internal/route/board.go +++ b/internal/route/board.go @@ -4,6 +4,8 @@ import ( "encoding/json" "net/http" + "github.com/go-chi/chi" + "forge.cadoles.com/wpetit/gitea-kan/internal/repository" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/middleware/container" @@ -18,6 +20,8 @@ func serveBoards(w http.ResponseWriter, r *http.Request) { panic(errors.Wrap(err, "could not retrieve boards list")) } + w.Header().Add("Content-Type", "application/json") + encoder := json.NewEncoder(w) if err := encoder.Encode(boards); err != nil { panic(errors.Wrap(err, "could not encode boards list")) @@ -39,8 +43,28 @@ func saveBoard(w http.ResponseWriter, r *http.Request) { panic(errors.Wrap(err, "could not save board")) } + w.Header().Add("Content-Type", "application/json") + encoder := json.NewEncoder(w) if err := encoder.Encode(board); err != nil { panic(errors.Wrap(err, "could not encode board")) } } + +func deleteBoard(w http.ResponseWriter, r *http.Request) { + boardID := repository.BoardID(chi.URLParam(r, "boardID")) + + ctn := container.Must(r.Context()) + repo := repository.Must(ctn) + + if err := repo.Boards().Delete(boardID); err != nil { + if err == repository.ErrNotFound { + http.NotFound(w, r) + return + } + + panic(err) + } + + w.WriteHeader(http.StatusNoContent) +} diff --git a/internal/route/route.go b/internal/route/route.go index c102ca3..53af29f 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -21,6 +21,7 @@ func Mount(r *chi.Mux, config *config.Config) { r.Get("/logout", handleLogout) r.Get("/api/boards", serveBoards) r.Post("/api/boards", saveBoard) + r.Delete("/api/boards/{boardID}", deleteBoard) r.Handle("/gitea/api/*", http.StripPrefix("/gitea", http.HandlerFunc(proxyAPIRequest))) r.Get("/*", static.Dir(config.HTTP.PublicDir, "", html5PushStateHandler)) })