From 6580d01370795a1af9475e01e0a0f329f5aab782 Mon Sep 17 00:00:00 2001 From: William Petit Date: Fri, 13 Dec 2019 12:00:07 +0100 Subject: [PATCH 1/8] Fix lane height display --- client/src/sass/_kanboard.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/sass/_kanboard.scss b/client/src/sass/_kanboard.scss index a14e555..e1e29a8 100644 --- a/client/src/sass/_kanboard.scss +++ b/client/src/sass/_kanboard.scss @@ -35,7 +35,6 @@ } [data-react-beautiful-dnd-droppable] { - height: calc(100vh) !important; min-height: calc(100vh) !important; } From e20bf045cfc758d9aa4f5ddaa7d0bb667a5a1f22 Mon Sep 17 00:00:00 2001 From: William Petit Date: Fri, 13 Dec 2019 12:00:25 +0100 Subject: [PATCH 2/8] Fix logo display in Firefox --- client/src/components/Navbar.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Navbar.jsx b/client/src/components/Navbar.jsx index 49f7e76..44ac82c 100644 --- a/client/src/components/Navbar.jsx +++ b/client/src/components/Navbar.jsx @@ -7,7 +7,7 @@ export class Navbar extends React.PureComponent {
- +

GenGitKan

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)) }) From 33a0c7850a3b367445144cb5b8ff251063ff7797 Mon Sep 17 00:00:00 2001 From: William Petit Date: Fri, 13 Dec 2019 13:30:33 +0100 Subject: [PATCH 5/8] Logout via AJAX --- client/src/components/App.jsx | 9 +++++++-- client/src/store/actions/logout.js | 6 ++++-- client/src/store/sagas/logout.js | 16 +++++++++++++++- client/src/store/sagas/root.js | 7 ++++--- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/client/src/components/App.jsx b/client/src/components/App.jsx index 4c2f39f..dc15da6 100644 --- a/client/src/components/App.jsx +++ b/client/src/components/App.jsx @@ -5,6 +5,7 @@ import { ConnectedBoardPage as BoardPage } from './BoardPage/BoardPage'; import { ConnectedEditBoardPage as EditBoardPage } from './BoardPage/EditBoardPage'; import { store } from '../store/store'; import { Provider } from 'react-redux'; +import { logout } from '../store/actions/logout'; export class App extends React.Component { render() { @@ -17,8 +18,8 @@ export class App extends React.Component { { - window.location = "/logout"; - return null; + this.logout(); + return ; }} /> } /> @@ -26,4 +27,8 @@ export class App extends React.Component { ); } + + logout() { + store.dispatch(logout()); + } } \ No newline at end of file diff --git a/client/src/store/actions/logout.js b/client/src/store/actions/logout.js index b54b03a..3537185 100644 --- a/client/src/store/actions/logout.js +++ b/client/src/store/actions/logout.js @@ -1,5 +1,7 @@ -export const LOGOUT = "LOGOUT"; +export const LOGOUT_REQUEST = "LOGOUT_REQUEST"; +export const LOGOUT_SUCCESS = "LOGOUT_SUCCESS"; +export const LOGOUT_FAILURE = "LOGOUT_FAILURE"; export function logout() { - return { type: LOGOUT }; + return { type: LOGOUT_REQUEST }; }; \ No newline at end of file diff --git a/client/src/store/sagas/logout.js b/client/src/store/sagas/logout.js index 6c5dced..26e3fe0 100644 --- a/client/src/store/sagas/logout.js +++ b/client/src/store/sagas/logout.js @@ -1,3 +1,17 @@ +import { call, put } from 'redux-saga/effects'; +import { LOGOUT_FAILURE, LOGOUT_SUCCESS } from '../actions/logout'; + export function* logoutSaga() { - window.location = '/logout'; + try { + yield call(fetch, '/logout', { mode: 'no-cors', credentials: 'include' }); + } catch(err) { + yield put({ type: LOGOUT_FAILURE, error: err }); + return; + } + + yield put({ type: LOGOUT_SUCCESS }); +} + +export function* logoutSuccessSaga() { + window.location.reload(); } \ No newline at end of file diff --git a/client/src/store/sagas/root.js b/client/src/store/sagas/root.js index 8c6b45a..d7a8bff 100644 --- a/client/src/store/sagas/root.js +++ b/client/src/store/sagas/root.js @@ -6,8 +6,8 @@ import { FETCH_ISSUES_REQUEST, ADD_LABEL_REQUEST, REMOVE_LABEL_REQUEST, CREATE_I import { fetchIssuesSaga, addLabelSaga, removeLabelSaga, createIssueSaga } from './issues'; import { FETCH_PROJECTS_REQUEST } from '../actions/projects'; import { fetchProjectsSaga } from './projects'; -import { LOGOUT } from '../actions/logout'; -import { logoutSaga } from './logout'; +import { LOGOUT_REQUEST, LOGOUT_SUCCESS } from '../actions/logout'; +import { logoutSaga, logoutSuccessSaga } from './logout'; import { BUILD_KANBOARD_REQUEST, MOVE_CARD } from '../actions/kanboards'; import { buildKanboardSaga, moveCardSaga, refreshKanboardSaga } from './kanboards'; @@ -25,7 +25,8 @@ export function* rootSaga() { takeEvery(REMOVE_LABEL_REQUEST, removeLabelSaga), takeLatest(CREATE_ISSUE_REQUEST, createIssueSaga), takeLatest(CREATE_ISSUE_SUCCESS, refreshKanboardSaga), - takeLatest(LOGOUT, logoutSaga) + takeLatest(LOGOUT_REQUEST, logoutSaga), + takeLatest(LOGOUT_SUCCESS, logoutSuccessSaga) ]); } From b087e50292184ceeb1051e05b333f89c601b13aa Mon Sep 17 00:00:00 2001 From: William Petit Date: Fri, 13 Dec 2019 13:30:53 +0100 Subject: [PATCH 6/8] Fix error when no lane is defined in board --- client/src/components/BoardPage/BoardPage.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/BoardPage/BoardPage.jsx b/client/src/components/BoardPage/BoardPage.jsx index 66ab9b8..cec2bbd 100644 --- a/client/src/components/BoardPage/BoardPage.jsx +++ b/client/src/components/BoardPage/BoardPage.jsx @@ -66,7 +66,7 @@ export class BoardPage extends React.Component { renderNewCardModal() { const { newCardModalActive, newCardLaneID } = this.state; const { board } = this.props; - if (!board) return null; + if (!board || !newCardLaneID) return null; return (
From 477f221b24d40471fb9cb8a10b08c7404f579ae0 Mon Sep 17 00:00:00 2001 From: William Petit Date: Fri, 13 Dec 2019 13:31:27 +0100 Subject: [PATCH 7/8] Add project and issue links in card --- client/src/components/BoardPage/IssueCard.jsx | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/client/src/components/BoardPage/IssueCard.jsx b/client/src/components/BoardPage/IssueCard.jsx index 3a2a09d..a193ed8 100644 --- a/client/src/components/BoardPage/IssueCard.jsx +++ b/client/src/components/BoardPage/IssueCard.jsx @@ -1,8 +1,13 @@ import React from 'react'; +const issueURLPattern = /(^https?:\/\/([^\/]))$/i; + export class IssueCard extends React.PureComponent { render() { const { card } = this.props; + const issueURLInfo = extractInfoFromIssueURL(card.issue.url); + const projectURL = `${issueURLInfo.baseURL}/${issueURLInfo.owner}/${issueURLInfo.projectName}`; + const issueURL = `${projectURL}/issues/${card.issue.number}`; return (
@@ -30,18 +35,28 @@ export class IssueCard extends React.PureComponent {
); } +} + +function extractInfoFromIssueURL(issueURL) { + const pattern = /^(https?:\/\/[^\/]+)\/api\/v1\/repos\/([^\/]+)\/([^\/]+)\/.*$/; + const matches = pattern.exec(issueURL); + return { + baseURL: matches[1], + owner: matches[2], + projectName: matches[3], + }; } \ No newline at end of file From a7f0eabb97b7efe02ab5790d1a4568d01fc3f524 Mon Sep 17 00:00:00 2001 From: William Petit Date: Fri, 13 Dec 2019 13:31:50 +0100 Subject: [PATCH 8/8] Show empty boards to all users --- client/src/store/selectors/boards.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/store/selectors/boards.js b/client/src/store/selectors/boards.js index 1348b93..77c485f 100644 --- a/client/src/store/selectors/boards.js +++ b/client/src/store/selectors/boards.js @@ -2,7 +2,7 @@ export function selectBoardByUserProjects(boardsByID, projectsByName) { const userProjects = Object.keys(projectsByName); return Object.keys(boardsByID).reduce((filteredBoardsByID, boardID) => { const board = boardsByID[boardID]; - const hasProject = board.projects.some(p => userProjects.indexOf(p) !== -1); + const hasProject = board.projects.length === 0 || board.projects.some(p => userProjects.indexOf(p) !== -1); if (hasProject) { filteredBoardsByID[boardID] = board; }