mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-12 08:25:44 +01:00
@@ -35,6 +35,7 @@ package sonia.scm;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base interface for all manager classes.
|
* Base interface for all manager classes.
|
||||||
@@ -82,11 +83,12 @@ public interface Manager<T extends ModelObject>
|
|||||||
* Returns all object of the store sorted by the given {@link java.util.Comparator}
|
* Returns all object of the store sorted by the given {@link java.util.Comparator}
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
|
* @param filter to filter the returned objects
|
||||||
* @param comparator to sort the returned objects
|
* @param comparator to sort the returned objects
|
||||||
* @since 1.4
|
* @since 1.4
|
||||||
* @return all object of the store sorted by the given {@link java.util.Comparator}
|
* @return all object of the store sorted by the given {@link java.util.Comparator}
|
||||||
*/
|
*/
|
||||||
Collection<T> getAll(Comparator<T> comparator);
|
Collection<T> getAll(Predicate<T> filter, Comparator<T> comparator);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns objects from the store which are starts at the given start
|
* Returns objects from the store which are starts at the given start
|
||||||
@@ -125,6 +127,7 @@ public interface Manager<T extends ModelObject>
|
|||||||
* <p>This default implementation reads all items, first, so you might want to adapt this
|
* <p>This default implementation reads all items, first, so you might want to adapt this
|
||||||
* whenever reading is expensive!</p>
|
* whenever reading is expensive!</p>
|
||||||
*
|
*
|
||||||
|
* @param filter to filter returned objects
|
||||||
* @param comparator to sort the returned objects
|
* @param comparator to sort the returned objects
|
||||||
* @param pageNumber the number of the page to be returned (zero based)
|
* @param pageNumber the number of the page to be returned (zero based)
|
||||||
* @param pageSize the size of the pages
|
* @param pageSize the size of the pages
|
||||||
@@ -134,8 +137,8 @@ public interface Manager<T extends ModelObject>
|
|||||||
* page. If the requested page number exceeds the existing pages, an
|
* page. If the requested page number exceeds the existing pages, an
|
||||||
* empty page result is returned.
|
* empty page result is returned.
|
||||||
*/
|
*/
|
||||||
default PageResult<T> getPage(Comparator<T> comparator, int pageNumber, int pageSize) {
|
default PageResult<T> getPage(Predicate<T> filter, Comparator<T> comparator, int pageNumber, int pageSize) {
|
||||||
return PageResult.createPage(getAll(comparator), pageNumber, pageSize);
|
return PageResult.createPage(getAll(filter, comparator), pageNumber, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ package sonia.scm;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic decorator for manager classes.
|
* Basic decorator for manager classes.
|
||||||
@@ -104,9 +105,9 @@ public class ManagerDecorator<T extends ModelObject> implements Manager<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<T> getAll(Comparator<T> comparator)
|
public Collection<T> getAll(Predicate<T> filter, Comparator<T> comparator)
|
||||||
{
|
{
|
||||||
return decorated.getAll(comparator);
|
return decorated.getAll(filter, comparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -92,8 +92,6 @@ public final class SearchUtil
|
|||||||
{
|
{
|
||||||
result = true;
|
result = true;
|
||||||
|
|
||||||
if (Util.isNotEmpty(other))
|
|
||||||
{
|
|
||||||
for (String o : other)
|
for (String o : other)
|
||||||
{
|
{
|
||||||
if ((o == null) ||!o.matches(query))
|
if ((o == null) ||!o.matches(query))
|
||||||
@@ -104,7 +102,6 @@ public final class SearchUtil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -126,8 +123,6 @@ public final class SearchUtil
|
|||||||
String query = createStringQuery(request);
|
String query = createStringQuery(request);
|
||||||
|
|
||||||
if (!value.matches(query))
|
if (!value.matches(query))
|
||||||
{
|
|
||||||
if (Util.isNotEmpty(other))
|
|
||||||
{
|
{
|
||||||
for (String o : other)
|
for (String o : other)
|
||||||
{
|
{
|
||||||
@@ -139,7 +134,6 @@ public final class SearchUtil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = true;
|
result = true;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.mockito.Mock;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
@@ -18,21 +19,22 @@ public class ManagerTest {
|
|||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private Comparator comparator;
|
private Comparator comparator;
|
||||||
|
private Predicate predicate = x -> true;
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void validatesPageNumber() {
|
public void validatesPageNumber() {
|
||||||
manager.getPage(comparator, -1, 5);
|
manager.getPage(predicate, comparator, -1, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void validatesPageSize() {
|
public void validatesPageSize() {
|
||||||
manager.getPage(comparator, 2, 0);
|
manager.getPage(predicate, comparator, 2, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getsNoPage() {
|
public void getsNoPage() {
|
||||||
givenItemCount = 0;
|
givenItemCount = 0;
|
||||||
PageResult singlePage = manager.getPage(comparator, 0, 5);
|
PageResult singlePage = manager.getPage(predicate, comparator, 0, 5);
|
||||||
assertEquals(0, singlePage.getEntities().size());
|
assertEquals(0, singlePage.getEntities().size());
|
||||||
assertEquals(givenItemCount, singlePage.getOverallCount());
|
assertEquals(givenItemCount, singlePage.getOverallCount());
|
||||||
}
|
}
|
||||||
@@ -40,7 +42,7 @@ public class ManagerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getsSinglePageWithoutEnoughItems() {
|
public void getsSinglePageWithoutEnoughItems() {
|
||||||
givenItemCount = 3;
|
givenItemCount = 3;
|
||||||
PageResult singlePage = manager.getPage(comparator, 0, 4);
|
PageResult singlePage = manager.getPage(predicate, comparator, 0, 4);
|
||||||
assertEquals(3, singlePage.getEntities().size() );
|
assertEquals(3, singlePage.getEntities().size() );
|
||||||
assertEquals(givenItemCount, singlePage.getOverallCount());
|
assertEquals(givenItemCount, singlePage.getOverallCount());
|
||||||
}
|
}
|
||||||
@@ -48,7 +50,7 @@ public class ManagerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getsSinglePageWithExactCountOfItems() {
|
public void getsSinglePageWithExactCountOfItems() {
|
||||||
givenItemCount = 3;
|
givenItemCount = 3;
|
||||||
PageResult singlePage = manager.getPage(comparator, 0, 3);
|
PageResult singlePage = manager.getPage(predicate, comparator, 0, 3);
|
||||||
assertEquals(3, singlePage.getEntities().size() );
|
assertEquals(3, singlePage.getEntities().size() );
|
||||||
assertEquals(givenItemCount, singlePage.getOverallCount());
|
assertEquals(givenItemCount, singlePage.getOverallCount());
|
||||||
}
|
}
|
||||||
@@ -56,11 +58,11 @@ public class ManagerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getsTwoPages() {
|
public void getsTwoPages() {
|
||||||
givenItemCount = 3;
|
givenItemCount = 3;
|
||||||
PageResult page1 = manager.getPage(comparator, 0, 2);
|
PageResult page1 = manager.getPage(predicate, comparator, 0, 2);
|
||||||
assertEquals(2, page1.getEntities().size());
|
assertEquals(2, page1.getEntities().size());
|
||||||
assertEquals(givenItemCount, page1.getOverallCount());
|
assertEquals(givenItemCount, page1.getOverallCount());
|
||||||
|
|
||||||
PageResult page2 = manager.getPage(comparator, 1, 2);
|
PageResult page2 = manager.getPage(predicate, comparator, 1, 2);
|
||||||
assertEquals(1, page2.getEntities().size());
|
assertEquals(1, page2.getEntities().size());
|
||||||
assertEquals(givenItemCount, page2.getOverallCount());
|
assertEquals(givenItemCount, page2.getOverallCount());
|
||||||
}
|
}
|
||||||
@@ -79,7 +81,7 @@ public class ManagerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection getAll(Comparator comparator) { return getAll(); }
|
public Collection getAll(Predicate filter, Comparator comparator) { return getAll(); }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection getAll(int start, int limit) { return null; }
|
public Collection getAll(int start, int limit) { return null; }
|
||||||
|
|||||||
@@ -12,6 +12,6 @@
|
|||||||
"@scm-manager/ui-extensions": "^0.1.2"
|
"@scm-manager/ui-extensions": "^0.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.26"
|
"@scm-manager/ui-bundler": "^0.0.28"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -707,9 +707,9 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.26":
|
"@scm-manager/ui-bundler@^0.0.28":
|
||||||
version "0.0.26"
|
version "0.0.28"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.26.tgz#4676a7079b781b33fa1989c6643205c3559b1f66"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.28.tgz#69df46f3bc8fc35ecff0d575d893704b7f731e1e"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
|
|||||||
@@ -9,6 +9,6 @@
|
|||||||
"@scm-manager/ui-extensions": "^0.1.2"
|
"@scm-manager/ui-extensions": "^0.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.26"
|
"@scm-manager/ui-bundler": "^0.0.28"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -641,9 +641,9 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.26":
|
"@scm-manager/ui-bundler@^0.0.28":
|
||||||
version "0.0.26"
|
version "0.0.28"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.26.tgz#4676a7079b781b33fa1989c6643205c3559b1f66"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.28.tgz#69df46f3bc8fc35ecff0d575d893704b7f731e1e"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
|
|||||||
@@ -9,6 +9,6 @@
|
|||||||
"@scm-manager/ui-extensions": "^0.1.2"
|
"@scm-manager/ui-extensions": "^0.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.26"
|
"@scm-manager/ui-bundler": "^0.0.28"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -641,9 +641,9 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.26":
|
"@scm-manager/ui-bundler@^0.0.28":
|
||||||
version "0.0.26"
|
version "0.0.28"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.26.tgz#4676a7079b781b33fa1989c6643205c3559b1f66"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.28.tgz#69df46f3bc8fc35ecff0d575d893704b7f731e1e"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"eslint-fix": "eslint src --fix"
|
"eslint-fix": "eslint src --fix"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.26",
|
"@scm-manager/ui-bundler": "^0.0.28",
|
||||||
"create-index": "^2.3.0",
|
"create-index": "^2.3.0",
|
||||||
"enzyme": "^3.5.0",
|
"enzyme": "^3.5.0",
|
||||||
"enzyme-adapter-react-16": "^1.3.1",
|
"enzyme-adapter-react-16": "^1.3.1",
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
"@scm-manager/ui-types": "2.0.0-SNAPSHOT",
|
"@scm-manager/ui-types": "2.0.0-SNAPSHOT",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
|
"query-string": "5",
|
||||||
"react": "^16.8.6",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.8.6",
|
"react-dom": "^16.8.6",
|
||||||
"react-diff-view": "^1.8.1",
|
"react-diff-view": "^1.8.1",
|
||||||
|
|||||||
@@ -7,12 +7,20 @@ import {Button} from "./buttons";
|
|||||||
type Props = {
|
type Props = {
|
||||||
collection: PagedCollection,
|
collection: PagedCollection,
|
||||||
page: number,
|
page: number,
|
||||||
|
filter?: string,
|
||||||
|
|
||||||
// context props
|
// context props
|
||||||
t: string => string
|
t: string => string
|
||||||
};
|
};
|
||||||
|
|
||||||
class LinkPaginator extends React.Component<Props> {
|
class LinkPaginator extends React.Component<Props> {
|
||||||
|
addFilterToLink(link: string) {
|
||||||
|
const { filter } = this.props;
|
||||||
|
if (filter) {
|
||||||
|
return `${link}?q=${filter}`;
|
||||||
|
}
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
renderFirstButton() {
|
renderFirstButton() {
|
||||||
return (
|
return (
|
||||||
@@ -20,7 +28,7 @@ class LinkPaginator extends React.Component<Props> {
|
|||||||
className={"pagination-link"}
|
className={"pagination-link"}
|
||||||
label={"1"}
|
label={"1"}
|
||||||
disabled={false}
|
disabled={false}
|
||||||
link={"1"}
|
link={this.addFilterToLink("1")}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -34,7 +42,7 @@ class LinkPaginator extends React.Component<Props> {
|
|||||||
className={className}
|
className={className}
|
||||||
label={label ? label : previousPage.toString()}
|
label={label ? label : previousPage.toString()}
|
||||||
disabled={!this.hasLink("prev")}
|
disabled={!this.hasLink("prev")}
|
||||||
link={`${previousPage}`}
|
link={this.addFilterToLink(`${previousPage}`)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -52,7 +60,7 @@ class LinkPaginator extends React.Component<Props> {
|
|||||||
className={className}
|
className={className}
|
||||||
label={label ? label : nextPage.toString()}
|
label={label ? label : nextPage.toString()}
|
||||||
disabled={!this.hasLink("next")}
|
disabled={!this.hasLink("next")}
|
||||||
link={`${nextPage}`}
|
link={this.addFilterToLink(`${nextPage}`)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -64,7 +72,7 @@ class LinkPaginator extends React.Component<Props> {
|
|||||||
className={"pagination-link"}
|
className={"pagination-link"}
|
||||||
label={`${collection.pageTotal}`}
|
label={`${collection.pageTotal}`}
|
||||||
disabled={false}
|
disabled={false}
|
||||||
link={`${collection.pageTotal}`}
|
link={this.addFilterToLink(`${collection.pageTotal}`)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -115,10 +123,15 @@ class LinkPaginator extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { t } = this.props;
|
const { collection, t } = this.props;
|
||||||
|
|
||||||
|
if(collection) {
|
||||||
return (
|
return (
|
||||||
<nav className="pagination is-centered" aria-label="pagination">
|
<nav className="pagination is-centered" aria-label="pagination">
|
||||||
{this.renderPreviousButton("pagination-previous", t("paginator.previous"))}
|
{this.renderPreviousButton(
|
||||||
|
"pagination-previous",
|
||||||
|
t("paginator.previous")
|
||||||
|
)}
|
||||||
<ul className="pagination-list">
|
<ul className="pagination-list">
|
||||||
{this.pageLinks().map((link, index) => {
|
{this.pageLinks().map((link, index) => {
|
||||||
return <li key={index}>{link}</li>;
|
return <li key={index}>{link}</li>;
|
||||||
@@ -128,6 +141,8 @@ class LinkPaginator extends React.Component<Props> {
|
|||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate("commons")(LinkPaginator);
|
export default translate("commons")(LinkPaginator);
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// @flow
|
||||||
|
import React from "react";
|
||||||
|
import type { History } from "history";
|
||||||
|
import { withRouter } from "react-router-dom";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import injectSheet from "react-jss";
|
||||||
|
import { FilterInput } from "./forms";
|
||||||
|
import { Button, urls } from "./index";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
showCreateButton: boolean,
|
||||||
|
link: string,
|
||||||
|
label?: string,
|
||||||
|
|
||||||
|
// context props
|
||||||
|
classes: Object,
|
||||||
|
history: History,
|
||||||
|
location: any
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
button: {
|
||||||
|
float: "right",
|
||||||
|
marginTop: "1.25rem",
|
||||||
|
marginLeft: "1.25rem"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class OverviewPageActions extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { history, location, link } = this.props;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FilterInput
|
||||||
|
value={urls.getQueryStringFromLocation(location)}
|
||||||
|
filter={filter => {
|
||||||
|
history.push(`/${link}/?q=${filter}`);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{this.renderCreateButton()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCreateButton() {
|
||||||
|
const { showCreateButton, classes, link, label } = this.props;
|
||||||
|
if (showCreateButton) {
|
||||||
|
return (
|
||||||
|
<div className={classNames(classes.button, "input-button control")}>
|
||||||
|
<Button label={label} link={`/${link}/create`} color="primary" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default injectSheet(styles)(withRouter(OverviewPageActions));
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
//@flow
|
||||||
|
import React from "react";
|
||||||
|
import injectSheet from "react-jss";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
filter: string => void,
|
||||||
|
value?: string,
|
||||||
|
|
||||||
|
// context props
|
||||||
|
classes: Object,
|
||||||
|
t: string => string
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
value: string
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
inputField: {
|
||||||
|
float: "right",
|
||||||
|
marginTop: "1.25rem"
|
||||||
|
},
|
||||||
|
inputHeight: {
|
||||||
|
height: "2.5rem"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FilterInput extends React.Component<Props, State> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { value: this.props.value ? this.props.value : "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = event => {
|
||||||
|
this.setState({ value: event.target.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSubmit = event => {
|
||||||
|
this.props.filter(this.state.value);
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { classes, t } = this.props;
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className={classNames(classes.inputField, "input-field")}
|
||||||
|
onSubmit={this.handleSubmit}
|
||||||
|
>
|
||||||
|
<div className="control has-icons-left">
|
||||||
|
<input
|
||||||
|
className={classNames(classes.inputHeight, "input")}
|
||||||
|
type="search"
|
||||||
|
placeholder={t("filterEntries")}
|
||||||
|
value={this.state.value}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
<span className="icon is-small is-left">
|
||||||
|
<i className="fas fa-search" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default injectSheet(styles)(translate("commons")(FilterInput));
|
||||||
@@ -5,6 +5,7 @@ export { default as AutocompleteAddEntryToTableField } from "./AutocompleteAddEn
|
|||||||
export { default as MemberNameTable } from "./MemberNameTable.js";
|
export { default as MemberNameTable } from "./MemberNameTable.js";
|
||||||
export { default as Checkbox } from "./Checkbox.js";
|
export { default as Checkbox } from "./Checkbox.js";
|
||||||
export { default as Radio } from "./Radio.js";
|
export { default as Radio } from "./Radio.js";
|
||||||
|
export { default as FilterInput } from "./FilterInput.js";
|
||||||
export { default as InputField } from "./InputField.js";
|
export { default as InputField } from "./InputField.js";
|
||||||
export { default as Select } from "./Select.js";
|
export { default as Select } from "./Select.js";
|
||||||
export { default as Textarea } from "./Textarea.js";
|
export { default as Textarea } from "./Textarea.js";
|
||||||
|
|||||||
@@ -22,12 +22,14 @@ export { default as ProtectedRoute } from "./ProtectedRoute.js";
|
|||||||
export { default as Help } from "./Help";
|
export { default as Help } from "./Help";
|
||||||
export { default as HelpIcon } from "./HelpIcon";
|
export { default as HelpIcon } from "./HelpIcon";
|
||||||
export { default as Tooltip } from "./Tooltip";
|
export { default as Tooltip } from "./Tooltip";
|
||||||
|
// TODO do we need this? getPageFromMatch is already exported by urls
|
||||||
export { getPageFromMatch } from "./urls";
|
export { getPageFromMatch } from "./urls";
|
||||||
export { default as Autocomplete} from "./Autocomplete";
|
export { default as Autocomplete} from "./Autocomplete";
|
||||||
export { default as BranchSelector } from "./BranchSelector";
|
export { default as BranchSelector } from "./BranchSelector";
|
||||||
export { default as MarkdownView } from "./MarkdownView";
|
export { default as MarkdownView } from "./MarkdownView";
|
||||||
export { default as SyntaxHighlighter } from "./SyntaxHighlighter";
|
export { default as SyntaxHighlighter } from "./SyntaxHighlighter";
|
||||||
export { default as ErrorBoundary } from "./ErrorBoundary";
|
export { default as ErrorBoundary } from "./ErrorBoundary";
|
||||||
|
export { default as OverviewPageActions } from "./OverviewPageActions.js";
|
||||||
|
|
||||||
export { apiClient } from "./apiclient.js";
|
export { apiClient } from "./apiclient.js";
|
||||||
export * from "./errors";
|
export * from "./errors";
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
//@flow
|
//@flow
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import injectSheet from "react-jss";
|
||||||
|
import classNames from "classnames";
|
||||||
import Loading from "./../Loading";
|
import Loading from "./../Loading";
|
||||||
import ErrorNotification from "./../ErrorNotification";
|
import ErrorNotification from "./../ErrorNotification";
|
||||||
import Title from "./Title";
|
import Title from "./Title";
|
||||||
import Subtitle from "./Subtitle";
|
import Subtitle from "./Subtitle";
|
||||||
import injectSheet from "react-jss";
|
|
||||||
import classNames from "classnames";
|
|
||||||
import PageActions from "./PageActions";
|
import PageActions from "./PageActions";
|
||||||
import ErrorBoundary from "../ErrorBoundary";
|
import ErrorBoundary from "../ErrorBoundary";
|
||||||
|
|
||||||
@@ -22,9 +22,9 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
spacing: {
|
actions: {
|
||||||
marginTop: "1.25rem",
|
display: "flex",
|
||||||
textAlign: "right"
|
justifyContent: "flex-end"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,28 +41,26 @@ class Page extends React.Component<Props> {
|
|||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPageHeader() {
|
renderPageHeader() {
|
||||||
const { title, subtitle, children, classes } = this.props;
|
const { error, title, subtitle, children, classes } = this.props;
|
||||||
|
|
||||||
let pageActions = null;
|
let pageActions = null;
|
||||||
let pageActionsExists = false;
|
let pageActionsExists = false;
|
||||||
React.Children.forEach(children, child => {
|
React.Children.forEach(children, child => {
|
||||||
if (child && child.type.name === PageActions.name) {
|
if (child && !error) {
|
||||||
|
if (child.type.name === PageActions.name)
|
||||||
pageActions = (
|
pageActions = (
|
||||||
<div className="column is-two-fifths">
|
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
classes.spacing,
|
classes.actions,
|
||||||
"is-mobile-create-button-spacing"
|
"column is-three-fifths is-mobile-action-spacing"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{child}
|
{child}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
pageActionsExists = true;
|
pageActionsExists = true;
|
||||||
}
|
}
|
||||||
@@ -97,9 +95,11 @@ class Page extends React.Component<Props> {
|
|||||||
|
|
||||||
let content = [];
|
let content = [];
|
||||||
React.Children.forEach(children, child => {
|
React.Children.forEach(children, child => {
|
||||||
if (child && child.type.name !== PageActions.name) {
|
if (child) {
|
||||||
|
if (child.type.name !== PageActions.name) {
|
||||||
content.push(child);
|
content.push(child);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,11 @@ import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
t: string => string,
|
t: string => string,
|
||||||
links: Links,
|
links: Links
|
||||||
};
|
};
|
||||||
|
|
||||||
class PrimaryNavigation extends React.Component<Props> {
|
class PrimaryNavigation extends React.Component<Props> {
|
||||||
|
createNavigationAppender = navigationItems => {
|
||||||
createNavigationAppender = (navigationItems) => {
|
|
||||||
const { t, links } = this.props;
|
const { t, links } = this.props;
|
||||||
|
|
||||||
return (to: string, match: string, label: string, linkName: string) => {
|
return (to: string, match: string, label: string, linkName: string) => {
|
||||||
@@ -24,8 +23,8 @@ class PrimaryNavigation extends React.Component<Props> {
|
|||||||
match={match}
|
match={match}
|
||||||
label={t(label)}
|
label={t(label)}
|
||||||
key={linkName}
|
key={linkName}
|
||||||
/>)
|
/>
|
||||||
;
|
);
|
||||||
navigationItems.push(navigationItem);
|
navigationItems.push(navigationItem);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -63,9 +62,19 @@ class PrimaryNavigation extends React.Component<Props> {
|
|||||||
<ExtensionPoint name="primary-navigation.first-menu" props={props} />
|
<ExtensionPoint name="primary-navigation.first-menu" props={props} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
append("/repos", "/(repo|repos)", "primary-navigation.repositories", "repositories");
|
append(
|
||||||
append("/users", "/(user|users)", "primary-navigation.users", "users");
|
"/repos/",
|
||||||
append("/groups", "/(group|groups)", "primary-navigation.groups", "groups");
|
"/(repo|repos)",
|
||||||
|
"primary-navigation.repositories",
|
||||||
|
"repositories"
|
||||||
|
);
|
||||||
|
append("/users/", "/(user|users)", "primary-navigation.users", "users");
|
||||||
|
append(
|
||||||
|
"/groups/",
|
||||||
|
"/(group|groups)",
|
||||||
|
"primary-navigation.groups",
|
||||||
|
"groups"
|
||||||
|
);
|
||||||
append("/config", "/config", "primary-navigation.config", "config");
|
append("/config", "/config", "primary-navigation.config", "config");
|
||||||
|
|
||||||
navigationItems.push(
|
navigationItems.push(
|
||||||
@@ -86,9 +95,7 @@ class PrimaryNavigation extends React.Component<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="tabs is-boxed">
|
<nav className="tabs is-boxed">
|
||||||
<ul>
|
<ul>{navigationItems}</ul>
|
||||||
{navigationItems}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
import queryString from "query-string";
|
||||||
|
|
||||||
export const contextPath = window.ctxPath || "";
|
export const contextPath = window.ctxPath || "";
|
||||||
|
|
||||||
export function withContextPath(path: string) {
|
export function withContextPath(path: string) {
|
||||||
@@ -27,3 +29,7 @@ export function getPageFromMatch(match: any) {
|
|||||||
}
|
}
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getQueryStringFromLocation(location: any) {
|
||||||
|
return location.search ? queryString.parse(location.search).q : undefined;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import {concat, getPageFromMatch, withEndingSlash} from "./urls";
|
import { concat, getPageFromMatch, getQueryStringFromLocation, withEndingSlash } from "./urls";
|
||||||
|
|
||||||
describe("tests for withEndingSlash", () => {
|
describe("tests for withEndingSlash", () => {
|
||||||
|
|
||||||
@@ -47,3 +47,28 @@ describe("tests for getPageFromMatch", () => {
|
|||||||
expect(getPageFromMatch(match)).toBe(42);
|
expect(getPageFromMatch(match)).toBe(42);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("tests for getQueryStringFromLocation", () => {
|
||||||
|
|
||||||
|
function createLocation(search: string) {
|
||||||
|
return {
|
||||||
|
search
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should return the query string", () => {
|
||||||
|
const location = createLocation("?q=abc");
|
||||||
|
expect(getQueryStringFromLocation(location)).toBe("abc");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return query string from multiple parameters", () => {
|
||||||
|
const location = createLocation("?x=a&y=b&q=abc&z=c");
|
||||||
|
expect(getQueryStringFromLocation(location)).toBe("abc");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined if q is not available", () => {
|
||||||
|
const location = createLocation("?x=a&y=b&z=c");
|
||||||
|
expect(getQueryStringFromLocation(location)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|||||||
@@ -693,9 +693,9 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.26":
|
"@scm-manager/ui-bundler@^0.0.28":
|
||||||
version "0.0.26"
|
version "0.0.28"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.26.tgz#4676a7079b781b33fa1989c6643205c3559b1f66"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.28.tgz#69df46f3bc8fc35ecff0d575d893704b7f731e1e"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
@@ -6530,6 +6530,14 @@ qs@~6.5.2:
|
|||||||
version "6.5.2"
|
version "6.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||||
|
|
||||||
|
query-string@5:
|
||||||
|
version "5.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb"
|
||||||
|
dependencies:
|
||||||
|
decode-uri-component "^0.2.0"
|
||||||
|
object-assign "^4.1.0"
|
||||||
|
strict-uri-encode "^1.0.0"
|
||||||
|
|
||||||
querystring-es3@~0.2.0:
|
querystring-es3@~0.2.0:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
||||||
@@ -7631,6 +7639,10 @@ stream-throttle@^0.1.3:
|
|||||||
commander "^2.2.0"
|
commander "^2.2.0"
|
||||||
limiter "^1.0.5"
|
limiter "^1.0.5"
|
||||||
|
|
||||||
|
strict-uri-encode@^1.0.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
|
||||||
|
|
||||||
string-length@^2.0.0:
|
string-length@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
|
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"check": "flow check"
|
"check": "flow check"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.26"
|
"@scm-manager/ui-bundler": "^0.0.28"
|
||||||
},
|
},
|
||||||
"browserify": {
|
"browserify": {
|
||||||
"transform": [
|
"transform": [
|
||||||
|
|||||||
@@ -707,9 +707,9 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.26":
|
"@scm-manager/ui-bundler@^0.0.28":
|
||||||
version "0.0.26"
|
version "0.0.28"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.26.tgz#4676a7079b781b33fa1989c6643205c3559b1f66"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.28.tgz#69df46f3bc8fc35ecff0d575d893704b7f731e1e"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
"pre-commit": "jest && flow && eslint src"
|
"pre-commit": "jest && flow && eslint src"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@scm-manager/ui-bundler": "^0.0.27",
|
"@scm-manager/ui-bundler": "^0.0.28",
|
||||||
"concat": "^1.0.3",
|
"concat": "^1.0.3",
|
||||||
"copyfiles": "^2.0.0",
|
"copyfiles": "^2.0.0",
|
||||||
"enzyme": "^3.3.0",
|
"enzyme": "^3.3.0",
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"groups": "Gruppen",
|
"groups": "Gruppen",
|
||||||
"config": "Einstellungen"
|
"config": "Einstellungen"
|
||||||
},
|
},
|
||||||
|
"filterEntries": "Einträge filtern",
|
||||||
"paginator": {
|
"paginator": {
|
||||||
"next": "Weiter",
|
"next": "Weiter",
|
||||||
"previous": "Zurück"
|
"previous": "Zurück"
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
"setPermissionsNavLink": "Berechtigungen"
|
"setPermissionsNavLink": "Berechtigungen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"addUser": {
|
"createUser": {
|
||||||
"title": "Benutzer erstellen",
|
"title": "Benutzer erstellen",
|
||||||
"subtitle": "Erstellen eines neuen Benutzers"
|
"subtitle": "Erstellen eines neuen Benutzers"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"groups": "Groups",
|
"groups": "Groups",
|
||||||
"config": "Configuration"
|
"config": "Configuration"
|
||||||
},
|
},
|
||||||
|
"filterEntries": "filter entries",
|
||||||
"paginator": {
|
"paginator": {
|
||||||
"next": "Next",
|
"next": "Next",
|
||||||
"previous": "Previous"
|
"previous": "Previous"
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
"setPermissionsNavLink": "Permissions"
|
"setPermissionsNavLink": "Permissions"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"addUser": {
|
"createUser": {
|
||||||
"title": "Create User",
|
"title": "Create User",
|
||||||
"subtitle": "Create a new user"
|
"subtitle": "Create a new user"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ import Logout from "../containers/Logout";
|
|||||||
import { ProtectedRoute } from "@scm-manager/ui-components";
|
import { ProtectedRoute } from "@scm-manager/ui-components";
|
||||||
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||||
|
|
||||||
import AddUser from "../users/containers/AddUser";
|
import CreateUser from "../users/containers/CreateUser";
|
||||||
import SingleUser from "../users/containers/SingleUser";
|
import SingleUser from "../users/containers/SingleUser";
|
||||||
import RepositoryRoot from "../repos/containers/RepositoryRoot";
|
import RepositoryRoot from "../repos/containers/RepositoryRoot";
|
||||||
import Create from "../repos/containers/Create";
|
import Create from "../repos/containers/Create";
|
||||||
|
|
||||||
import Groups from "../groups/containers/Groups";
|
import Groups from "../groups/containers/Groups";
|
||||||
import SingleGroup from "../groups/containers/SingleGroup";
|
import SingleGroup from "../groups/containers/SingleGroup";
|
||||||
import AddGroup from "../groups/containers/AddGroup";
|
import CreateGroup from "../groups/containers/CreateGroup";
|
||||||
|
|
||||||
import Config from "../config/containers/Config";
|
import Config from "../config/containers/Config";
|
||||||
import Profile from "./Profile";
|
import Profile from "./Profile";
|
||||||
@@ -74,8 +74,8 @@ class Main extends React.Component<Props> {
|
|||||||
/>
|
/>
|
||||||
<ProtectedRoute
|
<ProtectedRoute
|
||||||
authenticated={authenticated}
|
authenticated={authenticated}
|
||||||
path="/users/add"
|
path="/users/create"
|
||||||
component={AddUser}
|
component={CreateUser}
|
||||||
/>
|
/>
|
||||||
<ProtectedRoute
|
<ProtectedRoute
|
||||||
exact
|
exact
|
||||||
@@ -102,8 +102,8 @@ class Main extends React.Component<Props> {
|
|||||||
/>
|
/>
|
||||||
<ProtectedRoute
|
<ProtectedRoute
|
||||||
authenticated={authenticated}
|
authenticated={authenticated}
|
||||||
path="/groups/add"
|
path="/groups/create"
|
||||||
component={AddGroup}
|
component={CreateGroup}
|
||||||
/>
|
/>
|
||||||
<ProtectedRoute
|
<ProtectedRoute
|
||||||
exact
|
exact
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ class GroupForm extends React.Component<Props, State> {
|
|||||||
},
|
},
|
||||||
_links: {},
|
_links: {},
|
||||||
members: [],
|
members: [],
|
||||||
type: ""
|
type: "",
|
||||||
|
external: false
|
||||||
},
|
},
|
||||||
nameValidationError: false
|
nameValidationError: false
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
//@flow
|
|
||||||
import React from "react";
|
|
||||||
import { translate } from "react-i18next";
|
|
||||||
import { CreateButton } from "@scm-manager/ui-components";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
t: string => string
|
|
||||||
};
|
|
||||||
|
|
||||||
class CreateGroupButton extends React.Component<Props> {
|
|
||||||
render() {
|
|
||||||
const { t } = this.props;
|
|
||||||
return (
|
|
||||||
<CreateButton label={t("create-group-button.label")} link="/groups/add" />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default translate("groups")(CreateGroupButton);
|
|
||||||
@@ -31,7 +31,7 @@ type Props = {
|
|||||||
|
|
||||||
type State = {};
|
type State = {};
|
||||||
|
|
||||||
class AddGroup extends React.Component<Props, State> {
|
class CreateGroup extends React.Component<Props, State> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.resetForm();
|
this.props.resetForm();
|
||||||
}
|
}
|
||||||
@@ -104,4 +104,4 @@ const mapStateToProps = state => {
|
|||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(translate("groups")(AddGroup));
|
)(translate("groups")(CreateGroup));
|
||||||
@@ -2,27 +2,26 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
import type { Group, PagedCollection } from "@scm-manager/ui-types";
|
|
||||||
import type { History } from "history";
|
import type { History } from "history";
|
||||||
import {
|
import type { Group, PagedCollection } from "@scm-manager/ui-types";
|
||||||
Page,
|
|
||||||
PageActions,
|
|
||||||
Button,
|
|
||||||
Notification,
|
|
||||||
Paginator
|
|
||||||
} from "@scm-manager/ui-components";
|
|
||||||
import { GroupTable } from "./../components/table";
|
|
||||||
import CreateGroupButton from "../components/buttons/CreateGroupButton";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
fetchGroupsByPage,
|
fetchGroupsByPage,
|
||||||
fetchGroupsByLink,
|
|
||||||
getGroupsFromState,
|
getGroupsFromState,
|
||||||
isFetchGroupsPending,
|
isFetchGroupsPending,
|
||||||
getFetchGroupsFailure,
|
getFetchGroupsFailure,
|
||||||
isPermittedToCreateGroups,
|
isPermittedToCreateGroups,
|
||||||
selectListAsCollection
|
selectListAsCollection
|
||||||
} from "../modules/groups";
|
} from "../modules/groups";
|
||||||
|
import {
|
||||||
|
Page,
|
||||||
|
PageActions,
|
||||||
|
OverviewPageActions,
|
||||||
|
Notification,
|
||||||
|
LinkPaginator,
|
||||||
|
urls,
|
||||||
|
CreateButton
|
||||||
|
} from "@scm-manager/ui-components";
|
||||||
|
import { GroupTable } from "./../components/table";
|
||||||
import { getGroupsLink } from "../../modules/indexResource";
|
import { getGroupsLink } from "../../modules/indexResource";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -37,37 +36,45 @@ type Props = {
|
|||||||
// context objects
|
// context objects
|
||||||
t: string => string,
|
t: string => string,
|
||||||
history: History,
|
history: History,
|
||||||
|
location: any,
|
||||||
|
|
||||||
// dispatch functions
|
// dispatch functions
|
||||||
fetchGroupsByPage: (link: string, page: number) => void,
|
fetchGroupsByPage: (link: string, page: number, filter?: string) => void
|
||||||
fetchGroupsByLink: (link: string) => void
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Groups extends React.Component<Props> {
|
class Groups extends React.Component<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchGroupsByPage(this.props.groupLink, this.props.page);
|
const { fetchGroupsByPage, groupLink, page, location } = this.props;
|
||||||
|
fetchGroupsByPage(
|
||||||
|
groupLink,
|
||||||
|
page,
|
||||||
|
urls.getQueryStringFromLocation(location)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPageChange = (link: string) => {
|
|
||||||
this.props.fetchGroupsByLink(link);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* reflect page transitions in the uri
|
|
||||||
*/
|
|
||||||
componentDidUpdate = (prevProps: Props) => {
|
componentDidUpdate = (prevProps: Props) => {
|
||||||
const { page, list } = this.props;
|
const {
|
||||||
if (list.page >= 0) {
|
loading,
|
||||||
// backend starts paging by 0
|
list,
|
||||||
|
page,
|
||||||
|
groupLink,
|
||||||
|
location,
|
||||||
|
fetchGroupsByPage
|
||||||
|
} = this.props;
|
||||||
|
if (list && page && !loading) {
|
||||||
const statePage: number = list.page + 1;
|
const statePage: number = list.page + 1;
|
||||||
if (page !== statePage) {
|
if (page !== statePage || prevProps.location.search !== location.search) {
|
||||||
this.props.history.push(`/groups/${statePage}`);
|
fetchGroupsByPage(
|
||||||
|
groupLink,
|
||||||
|
page,
|
||||||
|
urls.getQueryStringFromLocation(location)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { groups, loading, error, t } = this.props;
|
const { groups, loading, error, canAddGroups, t } = this.props;
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
title={t("groups.title")}
|
title={t("groups.title")}
|
||||||
@@ -77,74 +84,56 @@ class Groups extends React.Component<Props> {
|
|||||||
>
|
>
|
||||||
{this.renderGroupTable()}
|
{this.renderGroupTable()}
|
||||||
{this.renderCreateButton()}
|
{this.renderCreateButton()}
|
||||||
{this.renderPageActionCreateButton()}
|
<PageActions>
|
||||||
|
<OverviewPageActions
|
||||||
|
showCreateButton={canAddGroups}
|
||||||
|
link="groups"
|
||||||
|
label={t("create-group-button.label")}
|
||||||
|
/>
|
||||||
|
</PageActions>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderGroupTable() {
|
renderGroupTable() {
|
||||||
const { groups, t } = this.props;
|
const { groups, list, page, location, t } = this.props;
|
||||||
if (groups && groups.length > 0) {
|
if (groups && groups.length > 0) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GroupTable groups={groups} />
|
<GroupTable groups={groups} />
|
||||||
{this.renderPaginator()}
|
<LinkPaginator
|
||||||
|
collection={list}
|
||||||
|
page={page}
|
||||||
|
filter={urls.getQueryStringFromLocation(location)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <Notification type="info">{t("groups.noGroups")}</Notification>;
|
return <Notification type="info">{t("groups.noGroups")}</Notification>;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPaginator() {
|
|
||||||
const { list } = this.props;
|
|
||||||
if (list) {
|
|
||||||
return <Paginator collection={list} onPageChange={this.onPageChange} />;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCreateButton() {
|
renderCreateButton() {
|
||||||
if (this.props.canAddGroups) {
|
const { canAddGroups, t } = this.props;
|
||||||
return <CreateGroupButton />;
|
if (canAddGroups) {
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPageActionCreateButton() {
|
|
||||||
if (this.props.canAddGroups) {
|
|
||||||
return (
|
return (
|
||||||
<PageActions>
|
<CreateButton
|
||||||
<Button
|
label={t("create-group-button.label")}
|
||||||
label={this.props.t("create-group-button.label")}
|
link="/groups/create"
|
||||||
link="/groups/add"
|
|
||||||
color="primary"
|
|
||||||
/>
|
/>
|
||||||
</PageActions>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPageFromProps = props => {
|
|
||||||
let page = props.match.params.page;
|
|
||||||
if (page) {
|
|
||||||
page = parseInt(page, 10);
|
|
||||||
} else {
|
|
||||||
page = 1;
|
|
||||||
}
|
|
||||||
return page;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
|
const { match } = ownProps;
|
||||||
const groups = getGroupsFromState(state);
|
const groups = getGroupsFromState(state);
|
||||||
const loading = isFetchGroupsPending(state);
|
const loading = isFetchGroupsPending(state);
|
||||||
const error = getFetchGroupsFailure(state);
|
const error = getFetchGroupsFailure(state);
|
||||||
|
const page = urls.getPageFromMatch(match);
|
||||||
const page = getPageFromProps(ownProps);
|
|
||||||
const canAddGroups = isPermittedToCreateGroups(state);
|
const canAddGroups = isPermittedToCreateGroups(state);
|
||||||
const list = selectListAsCollection(state);
|
const list = selectListAsCollection(state);
|
||||||
|
|
||||||
const groupLink = getGroupsLink(state);
|
const groupLink = getGroupsLink(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -160,11 +149,8 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
fetchGroupsByPage: (link: string, page: number) => {
|
fetchGroupsByPage: (link: string, page: number, filter?: string) => {
|
||||||
dispatch(fetchGroupsByPage(link, page));
|
dispatch(fetchGroupsByPage(link, page, filter));
|
||||||
},
|
|
||||||
fetchGroupsByLink: (link: string) => {
|
|
||||||
dispatch(fetchGroupsByLink(link));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,9 +40,14 @@ export function fetchGroups(link: string) {
|
|||||||
return fetchGroupsByLink(link);
|
return fetchGroupsByLink(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchGroupsByPage(link: string, page: number) {
|
export function fetchGroupsByPage(link: string, page: number, filter?: string) {
|
||||||
// backend start counting by 0
|
// backend start counting by 0
|
||||||
return fetchGroupsByLink(link + "?page=" + (page - 1));
|
if (filter) {
|
||||||
|
return fetchGroupsByLink(
|
||||||
|
`${link}?page=${page - 1}&q=${decodeURIComponent(filter)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return fetchGroupsByLink(`${link}?page=${page - 1}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchGroupsByLink(link: string) {
|
export function fetchGroupsByLink(link: string) {
|
||||||
|
|||||||
@@ -97,14 +97,14 @@ export const logoutPending = () => {
|
|||||||
|
|
||||||
export const logoutSuccess = () => {
|
export const logoutSuccess = () => {
|
||||||
return {
|
return {
|
||||||
type: LOGOUT_SUCCESS,
|
type: LOGOUT_SUCCESS
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const redirectAfterLogout = () => {
|
export const redirectAfterLogout = () => {
|
||||||
return {
|
return {
|
||||||
type: LOGOUT_REDIRECT
|
type: LOGOUT_REDIRECT
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const logoutFailure = (error: Error) => {
|
export const logoutFailure = (error: Error) => {
|
||||||
@@ -277,4 +277,3 @@ export const getLogoutFailure = (state: Object) => {
|
|||||||
export const isRedirecting = (state: Object) => {
|
export const isRedirecting = (state: Object) => {
|
||||||
return !!stateAuth(state).redirecting;
|
return !!stateAuth(state).redirecting;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import type {
|
|||||||
import { isPending } from "../../../modules/pending";
|
import { isPending } from "../../../modules/pending";
|
||||||
import { getFailure } from "../../../modules/failure";
|
import { getFailure } from "../../../modules/failure";
|
||||||
|
|
||||||
import memoizeOne from 'memoize-one';
|
import memoizeOne from "memoize-one";
|
||||||
|
|
||||||
export const FETCH_BRANCHES = "scm/repos/FETCH_BRANCHES";
|
export const FETCH_BRANCHES = "scm/repos/FETCH_BRANCHES";
|
||||||
export const FETCH_BRANCHES_PENDING = `${FETCH_BRANCHES}_${PENDING_SUFFIX}`;
|
export const FETCH_BRANCHES_PENDING = `${FETCH_BRANCHES}_${PENDING_SUFFIX}`;
|
||||||
@@ -111,9 +111,7 @@ export function createBranch(
|
|||||||
// Selectors
|
// Selectors
|
||||||
|
|
||||||
function collectBranches(repoState) {
|
function collectBranches(repoState) {
|
||||||
return repoState.list._embedded.branches.map(
|
return repoState.list._embedded.branches.map(name => repoState.byName[name]);
|
||||||
name => repoState.byName[name]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const memoizedBranchCollector = memoizeOne(collectBranches);
|
const memoizedBranchCollector = memoizeOne(collectBranches);
|
||||||
@@ -127,7 +125,12 @@ export function getBranches(state: Object, repository: Repository) {
|
|||||||
|
|
||||||
export function getBranchCreateLink(state: Object, repository: Repository) {
|
export function getBranchCreateLink(state: Object, repository: Repository) {
|
||||||
const repoState = getRepoState(state, repository);
|
const repoState = getRepoState(state, repository);
|
||||||
if (repoState && repoState.list && repoState.list._links && repoState.list._links.create) {
|
if (
|
||||||
|
repoState &&
|
||||||
|
repoState.list &&
|
||||||
|
repoState.list._links &&
|
||||||
|
repoState.list._links.create
|
||||||
|
) {
|
||||||
return repoState.list._links.create.href;
|
return repoState.list._links.create.href;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,71 +1,79 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import type { RepositoryCollection } from "@scm-manager/ui-types";
|
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
import { translate } from "react-i18next";
|
||||||
|
import type { History } from "history";
|
||||||
|
import { withRouter } from "react-router-dom";
|
||||||
|
import type { RepositoryCollection } from "@scm-manager/ui-types";
|
||||||
import {
|
import {
|
||||||
fetchRepos,
|
|
||||||
fetchReposByLink,
|
|
||||||
fetchReposByPage,
|
fetchReposByPage,
|
||||||
getFetchReposFailure,
|
getFetchReposFailure,
|
||||||
getRepositoryCollection,
|
getRepositoryCollection,
|
||||||
isAbleToCreateRepos,
|
isAbleToCreateRepos,
|
||||||
isFetchReposPending
|
isFetchReposPending
|
||||||
} from "../modules/repos";
|
} from "../modules/repos";
|
||||||
import { translate } from "react-i18next";
|
|
||||||
import {
|
import {
|
||||||
Page,
|
Page,
|
||||||
PageActions,
|
PageActions,
|
||||||
Button,
|
OverviewPageActions,
|
||||||
CreateButton,
|
CreateButton,
|
||||||
Notification,
|
Notification,
|
||||||
Paginator
|
LinkPaginator,
|
||||||
|
urls
|
||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import RepositoryList from "../components/list";
|
import RepositoryList from "../components/list";
|
||||||
import { withRouter } from "react-router-dom";
|
|
||||||
import type { History } from "history";
|
|
||||||
import { getRepositoriesLink } from "../../modules/indexResource";
|
import { getRepositoriesLink } from "../../modules/indexResource";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
page: number,
|
|
||||||
collection: RepositoryCollection,
|
|
||||||
loading: boolean,
|
loading: boolean,
|
||||||
error: Error,
|
error: Error,
|
||||||
showCreateButton: boolean,
|
showCreateButton: boolean,
|
||||||
|
collection: RepositoryCollection,
|
||||||
|
page: number,
|
||||||
reposLink: string,
|
reposLink: string,
|
||||||
|
|
||||||
// dispatched functions
|
|
||||||
fetchRepos: string => void,
|
|
||||||
fetchReposByPage: (string, number) => void,
|
|
||||||
fetchReposByLink: string => void,
|
|
||||||
|
|
||||||
// context props
|
// context props
|
||||||
t: string => string,
|
t: string => string,
|
||||||
history: History
|
history: History,
|
||||||
|
location: any,
|
||||||
|
|
||||||
|
// dispatched functions
|
||||||
|
fetchReposByPage: (link: string, page: number, filter?: string) => void
|
||||||
};
|
};
|
||||||
|
|
||||||
class Overview extends React.Component<Props> {
|
class Overview extends React.Component<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchReposByPage(this.props.reposLink, this.props.page);
|
const { fetchReposByPage, reposLink, page, location } = this.props;
|
||||||
|
fetchReposByPage(
|
||||||
|
reposLink,
|
||||||
|
page,
|
||||||
|
urls.getQueryStringFromLocation(location)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
componentDidUpdate = (prevProps: Props) => {
|
||||||
* reflect page transitions in the uri
|
const {
|
||||||
*/
|
loading,
|
||||||
componentDidUpdate() {
|
collection,
|
||||||
const { page, collection } = this.props;
|
page,
|
||||||
if (collection) {
|
reposLink,
|
||||||
// backend starts paging by 0
|
location,
|
||||||
|
fetchReposByPage
|
||||||
|
} = this.props;
|
||||||
|
if (collection && page && !loading) {
|
||||||
const statePage: number = collection.page + 1;
|
const statePage: number = collection.page + 1;
|
||||||
if (page !== statePage) {
|
if (page !== statePage || prevProps.location.search !== location.search) {
|
||||||
this.props.history.push(`/repos/${statePage}`);
|
fetchReposByPage(
|
||||||
}
|
reposLink,
|
||||||
|
page,
|
||||||
|
urls.getQueryStringFromLocation(location)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { error, loading, t } = this.props;
|
const { error, loading, showCreateButton, t } = this.props;
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
title={t("overview.title")}
|
title={t("overview.title")}
|
||||||
@@ -74,19 +82,29 @@ class Overview extends React.Component<Props> {
|
|||||||
error={error}
|
error={error}
|
||||||
>
|
>
|
||||||
{this.renderOverview()}
|
{this.renderOverview()}
|
||||||
{this.renderPageActionCreateButton()}
|
<PageActions>
|
||||||
|
<OverviewPageActions
|
||||||
|
showCreateButton={showCreateButton}
|
||||||
|
link="repos"
|
||||||
|
label={t("overview.createButton")}
|
||||||
|
/>
|
||||||
|
</PageActions>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderRepositoryList() {
|
renderRepositoryList() {
|
||||||
const { collection, fetchReposByLink, t } = this.props;
|
const { collection, page, location, t } = this.props;
|
||||||
|
|
||||||
if (collection._embedded && collection._embedded.repositories.length > 0) {
|
if (collection._embedded && collection._embedded.repositories.length > 0) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<RepositoryList repositories={collection._embedded.repositories} />
|
<RepositoryList repositories={collection._embedded.repositories} />
|
||||||
<Paginator collection={collection} onPageChange={fetchReposByLink} />
|
<LinkPaginator
|
||||||
|
collection={collection}
|
||||||
|
page={page}
|
||||||
|
filter={urls.getQueryStringFromLocation(location)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -99,10 +117,10 @@ class Overview extends React.Component<Props> {
|
|||||||
const { collection } = this.props;
|
const { collection } = this.props;
|
||||||
if (collection) {
|
if (collection) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
{this.renderRepositoryList()}
|
{this.renderRepositoryList()}
|
||||||
{this.renderCreateButton()}
|
{this.renderCreateButton()}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -117,61 +135,30 @@ class Overview extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPageActionCreateButton() {
|
|
||||||
const { showCreateButton, t } = this.props;
|
|
||||||
if (showCreateButton) {
|
|
||||||
return (
|
|
||||||
<PageActions>
|
|
||||||
<Button
|
|
||||||
label={t("overview.createButton")}
|
|
||||||
link="/repos/create"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</PageActions>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getPageFromProps = props => {
|
|
||||||
let page = props.match.params.page;
|
|
||||||
if (page) {
|
|
||||||
page = parseInt(page, 10);
|
|
||||||
} else {
|
|
||||||
page = 1;
|
|
||||||
}
|
|
||||||
return page;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
const page = getPageFromProps(ownProps);
|
const { match } = ownProps;
|
||||||
const collection = getRepositoryCollection(state);
|
const collection = getRepositoryCollection(state);
|
||||||
const loading = isFetchReposPending(state);
|
const loading = isFetchReposPending(state);
|
||||||
const error = getFetchReposFailure(state);
|
const error = getFetchReposFailure(state);
|
||||||
|
const page = urls.getPageFromMatch(match);
|
||||||
const showCreateButton = isAbleToCreateRepos(state);
|
const showCreateButton = isAbleToCreateRepos(state);
|
||||||
const reposLink = getRepositoriesLink(state);
|
const reposLink = getRepositoriesLink(state);
|
||||||
return {
|
return {
|
||||||
reposLink,
|
|
||||||
page,
|
|
||||||
collection,
|
collection,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
showCreateButton
|
page,
|
||||||
|
showCreateButton,
|
||||||
|
reposLink
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
fetchRepos: (link: string) => {
|
fetchReposByPage: (link: string, page: number, filter?: string) => {
|
||||||
dispatch(fetchRepos(link));
|
dispatch(fetchReposByPage(link, page, filter));
|
||||||
},
|
|
||||||
fetchReposByPage: (link: string, page: number) => {
|
|
||||||
dispatch(fetchReposByPage(link, page));
|
|
||||||
},
|
|
||||||
fetchReposByLink: (link: string) => {
|
|
||||||
dispatch(fetchReposByLink(link));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,7 +46,12 @@ export function fetchRepos(link: string) {
|
|||||||
return fetchReposByLink(link);
|
return fetchReposByLink(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchReposByPage(link: string, page: number) {
|
export function fetchReposByPage(link: string, page: number, filter?: string) {
|
||||||
|
if (filter) {
|
||||||
|
return fetchReposByLink(
|
||||||
|
`${link}?page=${page - 1}&q=${decodeURIComponent(filter)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
return fetchReposByLink(`${link}?page=${page - 1}`);
|
return fetchReposByLink(`${link}?page=${page - 1}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ type Props = {
|
|||||||
history: History
|
history: History
|
||||||
};
|
};
|
||||||
|
|
||||||
class AddUser extends React.Component<Props> {
|
class CreateUser extends React.Component<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.resetForm();
|
this.props.resetForm();
|
||||||
}
|
}
|
||||||
@@ -49,8 +49,8 @@ class AddUser extends React.Component<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
title={t("addUser.title")}
|
title={t("createUser.title")}
|
||||||
subtitle={t("addUser.subtitle")}
|
subtitle={t("createUser.subtitle")}
|
||||||
error={error}
|
error={error}
|
||||||
showContentOnError={true}
|
showContentOnError={true}
|
||||||
>
|
>
|
||||||
@@ -88,4 +88,4 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(translate("users")(AddUser));
|
)(translate("users")(CreateUser));
|
||||||
@@ -1,29 +1,27 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import type { History } from "history";
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { translate } from "react-i18next";
|
import { translate } from "react-i18next";
|
||||||
|
import type { History } from "history";
|
||||||
|
import type { User, PagedCollection } from "@scm-manager/ui-types";
|
||||||
import {
|
import {
|
||||||
fetchUsersByPage,
|
fetchUsersByPage,
|
||||||
fetchUsersByLink,
|
|
||||||
getUsersFromState,
|
getUsersFromState,
|
||||||
selectListAsCollection,
|
selectListAsCollection,
|
||||||
isPermittedToCreateUsers,
|
isPermittedToCreateUsers,
|
||||||
isFetchUsersPending,
|
isFetchUsersPending,
|
||||||
getFetchUsersFailure
|
getFetchUsersFailure
|
||||||
} from "../modules/users";
|
} from "../modules/users";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Page,
|
Page,
|
||||||
PageActions,
|
PageActions,
|
||||||
Button,
|
OverviewPageActions,
|
||||||
CreateButton,
|
Notification,
|
||||||
Paginator,
|
LinkPaginator,
|
||||||
Notification
|
urls,
|
||||||
|
CreateButton
|
||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import { UserTable } from "./../components/table";
|
import { UserTable } from "./../components/table";
|
||||||
import type { User, PagedCollection } from "@scm-manager/ui-types";
|
|
||||||
import { getUsersLink } from "../../modules/indexResource";
|
import { getUsersLink } from "../../modules/indexResource";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -38,37 +36,45 @@ type Props = {
|
|||||||
// context objects
|
// context objects
|
||||||
t: string => string,
|
t: string => string,
|
||||||
history: History,
|
history: History,
|
||||||
|
location: any,
|
||||||
|
|
||||||
// dispatch functions
|
// dispatch functions
|
||||||
fetchUsersByPage: (link: string, page: number) => void,
|
fetchUsersByPage: (link: string, page: number, filter?: string) => void
|
||||||
fetchUsersByLink: (link: string) => void
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Users extends React.Component<Props> {
|
class Users extends React.Component<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchUsersByPage(this.props.usersLink, this.props.page);
|
const { fetchUsersByPage, usersLink, page, location } = this.props;
|
||||||
|
fetchUsersByPage(
|
||||||
|
usersLink,
|
||||||
|
page,
|
||||||
|
urls.getQueryStringFromLocation(location)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPageChange = (link: string) => {
|
componentDidUpdate = (prevProps: Props) => {
|
||||||
this.props.fetchUsersByLink(link);
|
const {
|
||||||
|
loading,
|
||||||
|
list,
|
||||||
|
page,
|
||||||
|
usersLink,
|
||||||
|
location,
|
||||||
|
fetchUsersByPage
|
||||||
|
} = this.props;
|
||||||
|
if (list && page && !loading) {
|
||||||
|
const statePage: number = list.page + 1;
|
||||||
|
if (page !== statePage || prevProps.location.search !== location.search) {
|
||||||
|
fetchUsersByPage(
|
||||||
|
usersLink,
|
||||||
|
page,
|
||||||
|
urls.getQueryStringFromLocation(location)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* reflect page transitions in the uri
|
|
||||||
*/
|
|
||||||
componentDidUpdate() {
|
|
||||||
const { page, list } = this.props;
|
|
||||||
if (list && (list.page || list.page === 0)) {
|
|
||||||
// backend starts paging by 0
|
|
||||||
const statePage: number = list.page + 1;
|
|
||||||
if (page !== statePage) {
|
|
||||||
this.props.history.push(`/users/${statePage}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { users, loading, error, t } = this.props;
|
const { users, loading, error, canAddUsers, t } = this.props;
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
title={t("users.title")}
|
title={t("users.title")}
|
||||||
@@ -78,79 +84,54 @@ class Users extends React.Component<Props> {
|
|||||||
>
|
>
|
||||||
{this.renderUserTable()}
|
{this.renderUserTable()}
|
||||||
{this.renderCreateButton()}
|
{this.renderCreateButton()}
|
||||||
{this.renderPageActionCreateButton()}
|
<PageActions>
|
||||||
|
<OverviewPageActions
|
||||||
|
showCreateButton={canAddUsers}
|
||||||
|
link="users"
|
||||||
|
label={t("users.createButton")}
|
||||||
|
/>
|
||||||
|
</PageActions>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderUserTable() {
|
renderUserTable() {
|
||||||
const { users, t } = this.props;
|
const { users, list, page, location, t } = this.props;
|
||||||
if (users && users.length > 0) {
|
if (users && users.length > 0) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<UserTable users={users} />
|
<UserTable users={users} />
|
||||||
{this.renderPaginator()}
|
<LinkPaginator
|
||||||
|
collection={list}
|
||||||
|
page={page}
|
||||||
|
filter={urls.getQueryStringFromLocation(location)}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <Notification type="info">{t("users.noUsers")}</Notification>;
|
return <Notification type="info">{t("users.noUsers")}</Notification>;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPaginator() {
|
renderCreateButton() {
|
||||||
const { list } = this.props;
|
const { canAddUsers, t } = this.props;
|
||||||
if (list) {
|
if (canAddUsers) {
|
||||||
return <Paginator collection={list} onPageChange={this.onPageChange} />;
|
return (
|
||||||
|
<CreateButton label={t("users.createButton")} link="/users/create" />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCreateButton() {
|
|
||||||
const { t } = this.props;
|
|
||||||
if (this.props.canAddUsers) {
|
|
||||||
return <CreateButton label={t("users.createButton")} link="/users/add" />;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
renderPageActionCreateButton() {
|
|
||||||
const { t } = this.props;
|
|
||||||
if (this.props.canAddUsers) {
|
|
||||||
return (
|
|
||||||
<PageActions>
|
|
||||||
<Button
|
|
||||||
label={t("users.createButton")}
|
|
||||||
link="/users/add"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</PageActions>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getPageFromProps = props => {
|
|
||||||
let page = props.match.params.page;
|
|
||||||
if (page) {
|
|
||||||
page = parseInt(page, 10);
|
|
||||||
} else {
|
|
||||||
page = 1;
|
|
||||||
}
|
|
||||||
return page;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
|
const { match } = ownProps;
|
||||||
const users = getUsersFromState(state);
|
const users = getUsersFromState(state);
|
||||||
const loading = isFetchUsersPending(state);
|
const loading = isFetchUsersPending(state);
|
||||||
const error = getFetchUsersFailure(state);
|
const error = getFetchUsersFailure(state);
|
||||||
|
const page = urls.getPageFromMatch(match);
|
||||||
const usersLink = getUsersLink(state);
|
|
||||||
|
|
||||||
const page = getPageFromProps(ownProps);
|
|
||||||
const canAddUsers = isPermittedToCreateUsers(state);
|
const canAddUsers = isPermittedToCreateUsers(state);
|
||||||
const list = selectListAsCollection(state);
|
const list = selectListAsCollection(state);
|
||||||
|
const usersLink = getUsersLink(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
users,
|
users,
|
||||||
@@ -165,11 +146,8 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
fetchUsersByPage: (link: string, page: number) => {
|
fetchUsersByPage: (link: string, page: number, filter?: string) => {
|
||||||
dispatch(fetchUsersByPage(link, page));
|
dispatch(fetchUsersByPage(link, page, filter));
|
||||||
},
|
|
||||||
fetchUsersByLink: (link: string) => {
|
|
||||||
dispatch(fetchUsersByLink(link));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,9 +43,14 @@ export function fetchUsers(link: string) {
|
|||||||
return fetchUsersByLink(link);
|
return fetchUsersByLink(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchUsersByPage(link: string, page: number) {
|
export function fetchUsersByPage(link: string, page: number, filter?: string) {
|
||||||
// backend start counting by 0
|
// backend start counting by 0
|
||||||
return fetchUsersByLink(link + "?page=" + (page - 1));
|
if (filter) {
|
||||||
|
return fetchUsersByLink(
|
||||||
|
`${link}?page=${page - 1}&q=${decodeURIComponent(filter)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return fetchUsersByLink(`${link}?page=${page - 1}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchUsersByLink(link: string) {
|
export function fetchUsersByLink(link: string) {
|
||||||
@@ -153,9 +158,7 @@ export function createUser(link: string, user: User, callback?: () => void) {
|
|||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error =>
|
.catch(error => dispatch(createUserFailure(error)));
|
||||||
dispatch(createUserFailure(error))
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,8 +49,16 @@ hr.header-with-actions {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.is-mobile-create-button-spacing {
|
.is-mobile-action-spacing {
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
|
display: flow-root !important;
|
||||||
|
|
||||||
|
.input-field {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 0 1.25rem 0 !important;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.input-button {
|
||||||
border: 2px solid #e9f7fd;
|
border: 2px solid #e9f7fd;
|
||||||
padding: 1em 1em;
|
padding: 1em 1em;
|
||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
@@ -58,6 +66,7 @@ hr.header-with-actions {
|
|||||||
text-align: center !important;
|
text-align: center !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
|
|||||||
@@ -698,9 +698,9 @@
|
|||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
|
||||||
|
|
||||||
"@scm-manager/ui-bundler@^0.0.27":
|
"@scm-manager/ui-bundler@^0.0.28":
|
||||||
version "0.0.27"
|
version "0.0.28"
|
||||||
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.27.tgz#3ed2c7826780b9a1a9ea90464332640cfb5d54b5"
|
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.28.tgz#69df46f3bc8fc35ecff0d575d893704b7f731e1e"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.0.0"
|
"@babel/core" "^7.0.0"
|
||||||
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
"@babel/plugin-proposal-class-properties" "^7.0.0"
|
||||||
|
|||||||
@@ -52,12 +52,7 @@
|
|||||||
]]>
|
]]>
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
<api-classes>
|
<api-classes/>
|
||||||
<exclude pattern="sonia.scm.debug.DebugResource" />
|
|
||||||
<exclude pattern="sonia.scm.api.rest.resources.ConfigurationResource" />
|
|
||||||
<exclude pattern="sonia.scm.api.rest.resources.SupportResource" />
|
|
||||||
<exclude pattern="sonia.scm.api.rest.resources.RepositoryRootResource" />
|
|
||||||
</api-classes>
|
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
|
|
||||||
|
|||||||
@@ -1,581 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.api.rest.resources;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.net.UrlEscapers;
|
|
||||||
import org.apache.shiro.authz.AuthorizationException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import sonia.scm.LastModifiedAware;
|
|
||||||
import sonia.scm.Manager;
|
|
||||||
import sonia.scm.ModelObject;
|
|
||||||
import sonia.scm.PageResult;
|
|
||||||
import sonia.scm.api.rest.RestExceptionResult;
|
|
||||||
import sonia.scm.util.AssertUtil;
|
|
||||||
import sonia.scm.util.Comparables;
|
|
||||||
import sonia.scm.util.Util;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.CacheControl;
|
|
||||||
import javax.ws.rs.core.EntityTag;
|
|
||||||
import javax.ws.rs.core.GenericEntity;
|
|
||||||
import javax.ws.rs.core.Request;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.Response.Status;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
public abstract class AbstractManagerResource<T extends ModelObject> {
|
|
||||||
|
|
||||||
/** the logger for AbstractManagerResource */
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(AbstractManagerResource.class);
|
|
||||||
|
|
||||||
protected final Manager<T> manager;
|
|
||||||
private final Class<T> type;
|
|
||||||
|
|
||||||
protected int cacheMaxAge = 0;
|
|
||||||
protected boolean disableCache = false;
|
|
||||||
|
|
||||||
public AbstractManagerResource(Manager<T> manager, Class<T> type) {
|
|
||||||
this.manager = manager;
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param items
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected abstract GenericEntity<Collection<T>> createGenericEntity(
|
|
||||||
Collection<T> items);
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param item
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected abstract String getId(T item);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected abstract String getPathPart();
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param uriInfo
|
|
||||||
* @param item
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Response create(UriInfo uriInfo, T item)
|
|
||||||
{
|
|
||||||
preCreate(item);
|
|
||||||
|
|
||||||
Response response;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
manager.create(item);
|
|
||||||
|
|
||||||
String id = getId(item);
|
|
||||||
response = Response.created(location(uriInfo, id)).build();
|
|
||||||
}
|
|
||||||
catch (AuthorizationException ex)
|
|
||||||
{
|
|
||||||
logger.warn("create is not allowd", ex);
|
|
||||||
response = Response.status(Status.FORBIDDEN).build();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.error("error during create", ex);
|
|
||||||
response = createErrorResponse(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
URI location(UriInfo uriInfo, String id) {
|
|
||||||
String escaped = UrlEscapers.urlPathSegmentEscaper().escape(id);
|
|
||||||
return uriInfo.getAbsolutePath().resolve(getPathPart().concat("/").concat(escaped));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Response delete(String name)
|
|
||||||
{
|
|
||||||
Response response = null;
|
|
||||||
T item = manager.get(name);
|
|
||||||
|
|
||||||
if (item != null)
|
|
||||||
{
|
|
||||||
preDelete(item);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
manager.delete(item);
|
|
||||||
response = Response.noContent().build();
|
|
||||||
}
|
|
||||||
catch (AuthorizationException ex)
|
|
||||||
{
|
|
||||||
logger.warn("delete not allowd", ex);
|
|
||||||
response = Response.status(Response.Status.FORBIDDEN).build();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.error("error during delete", ex);
|
|
||||||
response = createErrorResponse(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* @param item
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Response update(String name, T item)
|
|
||||||
{
|
|
||||||
Response response = null;
|
|
||||||
|
|
||||||
preUpdate(item);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
manager.modify(item);
|
|
||||||
response = Response.noContent().build();
|
|
||||||
}
|
|
||||||
catch (AuthorizationException ex)
|
|
||||||
{
|
|
||||||
logger.warn("update not allowed", ex);
|
|
||||||
response = Response.status(Response.Status.FORBIDDEN).build();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.error("error during update", ex);
|
|
||||||
response = createErrorResponse(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* @param id
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Response get(Request request, String id)
|
|
||||||
{
|
|
||||||
Response response;
|
|
||||||
T item = manager.get(id);
|
|
||||||
|
|
||||||
if (item != null)
|
|
||||||
{
|
|
||||||
prepareForReturn(item);
|
|
||||||
|
|
||||||
if (disableCache)
|
|
||||||
{
|
|
||||||
response = Response.ok(item).build();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response = createCacheResponse(request, item, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response = Response.status(Response.Status.NOT_FOUND).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* @param start
|
|
||||||
* @param limit
|
|
||||||
* @param sortby
|
|
||||||
* @param desc
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Response getAll(Request request, int start, int limit, String sortby,
|
|
||||||
boolean desc)
|
|
||||||
{
|
|
||||||
Collection<T> items = fetchItems(sortby, desc, start, limit);
|
|
||||||
|
|
||||||
if (Util.isNotEmpty(items))
|
|
||||||
{
|
|
||||||
items = prepareForReturn(items);
|
|
||||||
}
|
|
||||||
|
|
||||||
Response response = null;
|
|
||||||
Object entity = createGenericEntity(items);
|
|
||||||
|
|
||||||
if (disableCache)
|
|
||||||
{
|
|
||||||
response = Response.ok(entity).build();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response = createCacheResponse(request, manager, items, entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public int getCacheMaxAge()
|
|
||||||
{
|
|
||||||
return cacheMaxAge;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public boolean isDisableCache()
|
|
||||||
{
|
|
||||||
return disableCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param cacheMaxAge
|
|
||||||
*/
|
|
||||||
public void setCacheMaxAge(int cacheMaxAge)
|
|
||||||
{
|
|
||||||
this.cacheMaxAge = cacheMaxAge;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param disableCache
|
|
||||||
*/
|
|
||||||
public void setDisableCache(boolean disableCache)
|
|
||||||
{
|
|
||||||
this.disableCache = disableCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param throwable
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected Response createErrorResponse(Throwable throwable)
|
|
||||||
{
|
|
||||||
return createErrorResponse(Status.INTERNAL_SERVER_ERROR,
|
|
||||||
throwable.getMessage(), throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param status
|
|
||||||
* @param message
|
|
||||||
* @param throwable
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected Response createErrorResponse(Status status, String message,
|
|
||||||
Throwable throwable)
|
|
||||||
{
|
|
||||||
return Response.status(status).entity(new RestExceptionResult(message,
|
|
||||||
throwable)).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param item
|
|
||||||
*/
|
|
||||||
protected void preCreate(T item) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param item
|
|
||||||
*/
|
|
||||||
protected void preDelete(T item) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param item
|
|
||||||
*/
|
|
||||||
protected void preUpdate(T item) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param item
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected T prepareForReturn(T item)
|
|
||||||
{
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param items
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
protected Collection<T> prepareForReturn(Collection<T> items)
|
|
||||||
{
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param rb
|
|
||||||
*/
|
|
||||||
private void addCacheControl(Response.ResponseBuilder rb)
|
|
||||||
{
|
|
||||||
CacheControl cc = new CacheControl();
|
|
||||||
|
|
||||||
cc.setMaxAge(cacheMaxAge);
|
|
||||||
rb.cacheControl(cc);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* @param timeItem
|
|
||||||
* @param item
|
|
||||||
* @param <I>
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private <I> Response createCacheResponse(Request request,
|
|
||||||
LastModifiedAware timeItem, I item)
|
|
||||||
{
|
|
||||||
return createCacheResponse(request, timeItem, item, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* @param timeItem
|
|
||||||
* @param entityItem
|
|
||||||
* @param item
|
|
||||||
* @param <I>
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private <I> Response createCacheResponse(Request request,
|
|
||||||
LastModifiedAware timeItem, Object entityItem, I item)
|
|
||||||
{
|
|
||||||
Response.ResponseBuilder builder = null;
|
|
||||||
Date lastModified = getLastModified(timeItem);
|
|
||||||
EntityTag e = new EntityTag(Integer.toString(entityItem.hashCode()));
|
|
||||||
|
|
||||||
if (lastModified != null)
|
|
||||||
{
|
|
||||||
builder = request.evaluatePreconditions(lastModified, e);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder = request.evaluatePreconditions(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (builder == null)
|
|
||||||
{
|
|
||||||
builder = Response.ok(item).tag(e).lastModified(lastModified);
|
|
||||||
}
|
|
||||||
|
|
||||||
addCacheControl(builder);
|
|
||||||
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Comparator<T> createComparator(String sortBy, boolean desc) {
|
|
||||||
Comparator<T> comparator = Comparables.comparator(type, sortBy);
|
|
||||||
if (desc) {
|
|
||||||
comparator = comparator.reversed();
|
|
||||||
}
|
|
||||||
return comparator;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Collection<T> fetchItems(String sortBy, boolean desc, int start,
|
|
||||||
int limit)
|
|
||||||
{
|
|
||||||
AssertUtil.assertPositive(start);
|
|
||||||
|
|
||||||
Collection<T> items = null;
|
|
||||||
|
|
||||||
if (limit > 0)
|
|
||||||
{
|
|
||||||
if (Util.isEmpty(sortBy))
|
|
||||||
{
|
|
||||||
|
|
||||||
// replace with something useful
|
|
||||||
sortBy = "id";
|
|
||||||
}
|
|
||||||
|
|
||||||
items = manager.getAll(createComparator(sortBy, desc), start, limit);
|
|
||||||
}
|
|
||||||
else if (Util.isNotEmpty(sortBy))
|
|
||||||
{
|
|
||||||
items = manager.getAll(createComparator(sortBy, desc));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
items = manager.getAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected PageResult<T> fetchPage(String sortBy, boolean desc, int pageNumber,
|
|
||||||
int pageSize) {
|
|
||||||
AssertUtil.assertPositive(pageNumber);
|
|
||||||
AssertUtil.assertPositive(pageSize);
|
|
||||||
|
|
||||||
if (Util.isEmpty(sortBy)) {
|
|
||||||
// replace with something useful
|
|
||||||
sortBy = "id";
|
|
||||||
}
|
|
||||||
|
|
||||||
return manager.getPage(createComparator(sortBy, desc), pageNumber, pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param item
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private Date getLastModified(LastModifiedAware item)
|
|
||||||
{
|
|
||||||
Date lastModified = null;
|
|
||||||
Long l = item.getLastModified();
|
|
||||||
|
|
||||||
if (l != null)
|
|
||||||
{
|
|
||||||
lastModified = new Date(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lastModified;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package sonia.scm.api.rest.resources;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import sonia.scm.repository.api.CatCommandBuilder;
|
|
||||||
import sonia.scm.repository.api.RepositoryService;
|
|
||||||
import sonia.scm.util.IOUtil;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.StreamingOutput;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public class BrowserStreamingOutput implements StreamingOutput {
|
|
||||||
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(BrowserStreamingOutput.class);
|
|
||||||
|
|
||||||
private final CatCommandBuilder builder;
|
|
||||||
private final String path;
|
|
||||||
private final RepositoryService repositoryService;
|
|
||||||
|
|
||||||
public BrowserStreamingOutput(RepositoryService repositoryService,
|
|
||||||
CatCommandBuilder builder, String path) {
|
|
||||||
this.repositoryService = repositoryService;
|
|
||||||
this.builder = builder;
|
|
||||||
this.path = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream output) throws IOException {
|
|
||||||
try {
|
|
||||||
builder.retriveContent(output, path);
|
|
||||||
} finally {
|
|
||||||
IOUtil.close(repositoryService);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.api.rest.resources;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
|
||||||
import org.apache.shiro.SecurityUtils;
|
|
||||||
import org.apache.shiro.authc.credential.PasswordService;
|
|
||||||
import org.apache.shiro.subject.Subject;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import sonia.scm.api.rest.RestActionResult;
|
|
||||||
import sonia.scm.security.Role;
|
|
||||||
import sonia.scm.security.ScmSecurityException;
|
|
||||||
import sonia.scm.user.User;
|
|
||||||
import sonia.scm.user.UserManager;
|
|
||||||
import sonia.scm.util.AssertUtil;
|
|
||||||
|
|
||||||
import javax.ws.rs.FormParam;
|
|
||||||
import javax.ws.rs.POST;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.Produces;
|
|
||||||
import javax.ws.rs.WebApplicationException;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resource to change the password of the authenticated user.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@Path("action/change-password")
|
|
||||||
public class ChangePasswordResource
|
|
||||||
{
|
|
||||||
|
|
||||||
/** the logger for ChangePasswordResource */
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(ChangePasswordResource.class);
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param userManager
|
|
||||||
* @param encryptionHandler
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
public ChangePasswordResource(UserManager userManager,
|
|
||||||
PasswordService encryptionHandler)
|
|
||||||
{
|
|
||||||
this.userManager = userManager;
|
|
||||||
this.passwordService = encryptionHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the password of the current user.
|
|
||||||
*
|
|
||||||
* @param oldPassword old password of the current user
|
|
||||||
* @param newPassword new password for the current user
|
|
||||||
*/
|
|
||||||
@POST
|
|
||||||
@TypeHint(RestActionResult.class)
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 400, condition = "bad request, the old password is not correct"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public Response changePassword(@FormParam("old-password") String oldPassword, @FormParam("new-password") String newPassword) {
|
|
||||||
AssertUtil.assertIsNotEmpty(oldPassword);
|
|
||||||
AssertUtil.assertIsNotEmpty(newPassword);
|
|
||||||
|
|
||||||
int length = newPassword.length();
|
|
||||||
|
|
||||||
if ((length < 6) || (length > 32))
|
|
||||||
{
|
|
||||||
throw new WebApplicationException(Response.Status.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
Response response = null;
|
|
||||||
Subject subject = SecurityUtils.getSubject();
|
|
||||||
|
|
||||||
if (!subject.hasRole(Role.USER))
|
|
||||||
{
|
|
||||||
throw new ScmSecurityException("user is not authenticated");
|
|
||||||
}
|
|
||||||
|
|
||||||
User currentUser = subject.getPrincipals().oneByType(User.class);
|
|
||||||
|
|
||||||
if (logger.isInfoEnabled())
|
|
||||||
{
|
|
||||||
logger.info("password change for user {}", currentUser.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only account of the default type can change their password
|
|
||||||
if (currentUser.getType().equals(userManager.getDefaultType()))
|
|
||||||
{
|
|
||||||
User dbUser = userManager.get(currentUser.getName());
|
|
||||||
|
|
||||||
if (passwordService.passwordsMatch(oldPassword, dbUser.getPassword()))
|
|
||||||
{
|
|
||||||
dbUser.setPassword(passwordService.encryptPassword(newPassword));
|
|
||||||
userManager.modify(dbUser);
|
|
||||||
response = Response.ok(new RestActionResult(true)).build();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response = Response.status(Response.Status.BAD_REQUEST).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//J-
|
|
||||||
logger.error(
|
|
||||||
"Only account of the default type ({}) can change their password",
|
|
||||||
userManager.getDefaultType()
|
|
||||||
);
|
|
||||||
//J+
|
|
||||||
response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final PasswordService passwordService;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final UserManager userManager;
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.api.rest.resources;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import sonia.scm.repository.api.DiffCommandBuilder;
|
|
||||||
import sonia.scm.repository.api.RepositoryService;
|
|
||||||
import sonia.scm.util.IOUtil;
|
|
||||||
|
|
||||||
import javax.ws.rs.WebApplicationException;
|
|
||||||
import javax.ws.rs.core.StreamingOutput;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
public class DiffStreamingOutput implements StreamingOutput
|
|
||||||
{
|
|
||||||
|
|
||||||
/** the logger for DiffStreamingOutput */
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(DiffStreamingOutput.class);
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param repositoryService
|
|
||||||
* @param builder
|
|
||||||
*/
|
|
||||||
public DiffStreamingOutput(RepositoryService repositoryService,
|
|
||||||
DiffCommandBuilder builder)
|
|
||||||
{
|
|
||||||
this.repositoryService = repositoryService;
|
|
||||||
this.builder = builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param output
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
* @throws WebApplicationException
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void write(OutputStream output) throws IOException {
|
|
||||||
try
|
|
||||||
{
|
|
||||||
builder.retrieveContent(output);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
IOUtil.close(repositoryService);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final DiffCommandBuilder builder;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final RepositoryService repositoryService;
|
|
||||||
}
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.api.rest.resources;
|
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
|
||||||
import com.google.common.collect.Collections2;
|
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
import com.google.common.collect.Ordering;
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import sonia.scm.repository.Repository;
|
|
||||||
import sonia.scm.repository.RepositoryManager;
|
|
||||||
import sonia.scm.repository.RepositoryTypePredicate;
|
|
||||||
import sonia.scm.template.Viewable;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.PathParam;
|
|
||||||
import javax.ws.rs.Produces;
|
|
||||||
import javax.ws.rs.core.Context;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@Path("help/repository-root/{type}.html")
|
|
||||||
public class RepositoryRootResource
|
|
||||||
{
|
|
||||||
|
|
||||||
private static final String TEMPLATE = "/templates/repository-root.mustache";
|
|
||||||
|
|
||||||
private final RepositoryManager repositoryManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
* @param repositoryManager
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
public RepositoryRootResource(RepositoryManager repositoryManager)
|
|
||||||
{
|
|
||||||
this.repositoryManager = repositoryManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* @param type
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Produces(MediaType.TEXT_HTML)
|
|
||||||
public Viewable renderRepositoriesRoot(@Context HttpServletRequest request, @PathParam("type") final String type)
|
|
||||||
{
|
|
||||||
//J-
|
|
||||||
Collection<RepositoryTemplateElement> unsortedRepositories =
|
|
||||||
Collections2.transform(
|
|
||||||
Collections2.filter(
|
|
||||||
repositoryManager.getAll(), new RepositoryTypePredicate(type))
|
|
||||||
, new RepositoryTransformFunction()
|
|
||||||
);
|
|
||||||
|
|
||||||
List<RepositoryTemplateElement> repositories = Ordering.from(
|
|
||||||
new RepositoryTemplateElementComparator()
|
|
||||||
).sortedCopy(unsortedRepositories);
|
|
||||||
//J+
|
|
||||||
Map<String, Object> environment = Maps.newHashMap();
|
|
||||||
|
|
||||||
environment.put("repositories", repositories);
|
|
||||||
|
|
||||||
return new Viewable(TEMPLATE, environment);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- inner classes --------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @version Enter version here..., 12/05/28
|
|
||||||
* @author Enter your name here...
|
|
||||||
*/
|
|
||||||
public static class RepositoryTemplateElement
|
|
||||||
{
|
|
||||||
|
|
||||||
public RepositoryTemplateElement(Repository repository)
|
|
||||||
{
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods --------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String getName()
|
|
||||||
{
|
|
||||||
return repository.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Repository getRepository()
|
|
||||||
{
|
|
||||||
return repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields -------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private Repository repository;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @version Enter version here..., 12/05/29
|
|
||||||
* @author Enter your name here...
|
|
||||||
*/
|
|
||||||
private static class RepositoryTemplateElementComparator
|
|
||||||
implements Comparator<RepositoryTemplateElement>
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param left
|
|
||||||
* @param right
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int compare(RepositoryTemplateElement left,
|
|
||||||
RepositoryTemplateElement right)
|
|
||||||
{
|
|
||||||
return left.getName().compareTo(right.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @version Enter version here..., 12/05/28
|
|
||||||
* @author Enter your name here...
|
|
||||||
*/
|
|
||||||
private static class RepositoryTransformFunction
|
|
||||||
implements Function<Repository, RepositoryTemplateElement>
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public RepositoryTemplateElement apply(Repository repository)
|
|
||||||
{
|
|
||||||
return new RepositoryTemplateElement(repository);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.api.rest.resources;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.github.legman.Subscribe;
|
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
|
||||||
import com.google.inject.Inject;
|
|
||||||
import com.google.inject.Singleton;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
|
||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
|
||||||
|
|
||||||
import sonia.scm.cache.Cache;
|
|
||||||
import sonia.scm.cache.CacheManager;
|
|
||||||
import sonia.scm.group.Group;
|
|
||||||
import sonia.scm.group.GroupEvent;
|
|
||||||
import sonia.scm.group.GroupManager;
|
|
||||||
import sonia.scm.search.SearchHandler;
|
|
||||||
import sonia.scm.search.SearchResult;
|
|
||||||
import sonia.scm.search.SearchResults;
|
|
||||||
import sonia.scm.user.User;
|
|
||||||
import sonia.scm.user.UserEvent;
|
|
||||||
import sonia.scm.user.UserManager;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.Produces;
|
|
||||||
import javax.ws.rs.QueryParam;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RESTful Web Service Resource to search users and groups. This endpoint can be used to implement typeahead input
|
|
||||||
* fields for permissions.
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
@Path("search")
|
|
||||||
public class SearchResource
|
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String CACHE_GROUP = "sonia.cache.search.groups";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String CACHE_USER = "sonia.cache.search.users";
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param userManager
|
|
||||||
* @param groupManager
|
|
||||||
* @param cacheManager
|
|
||||||
*/
|
|
||||||
@Inject
|
|
||||||
public SearchResource(UserManager userManager, GroupManager groupManager,
|
|
||||||
CacheManager cacheManager)
|
|
||||||
{
|
|
||||||
|
|
||||||
// create user searchhandler
|
|
||||||
Cache<String, SearchResults> userCache = cacheManager.getCache(CACHE_USER);
|
|
||||||
|
|
||||||
this.userSearchHandler = new SearchHandler<User>(userCache, userManager);
|
|
||||||
|
|
||||||
// create group searchhandler
|
|
||||||
Cache<String, SearchResults> groupCache =
|
|
||||||
cacheManager.getCache(CACHE_GROUP);
|
|
||||||
|
|
||||||
this.groupSearchHandler = new SearchHandler<Group>(groupCache,
|
|
||||||
groupManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
*/
|
|
||||||
@Subscribe
|
|
||||||
public void onEvent(UserEvent event)
|
|
||||||
{
|
|
||||||
if (event.getEventType().isPost())
|
|
||||||
{
|
|
||||||
userSearchHandler.clearCache();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param event
|
|
||||||
*/
|
|
||||||
@Subscribe
|
|
||||||
public void onEvent(GroupEvent event)
|
|
||||||
{
|
|
||||||
if (event.getEventType().isPost())
|
|
||||||
{
|
|
||||||
groupSearchHandler.clearCache();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of groups found by the given search string.
|
|
||||||
*
|
|
||||||
* @param queryString the search string
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Path("groups")
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public SearchResults searchGroups(@QueryParam("query") String queryString)
|
|
||||||
{
|
|
||||||
return groupSearchHandler.search(queryString,
|
|
||||||
new Function<Group, SearchResult>()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public SearchResult apply(Group group)
|
|
||||||
{
|
|
||||||
String label = group.getName();
|
|
||||||
String description = group.getDescription();
|
|
||||||
|
|
||||||
if (description != null)
|
|
||||||
{
|
|
||||||
label = label.concat(" (").concat(description).concat(")");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SearchResult(group.getName(), label);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of users found by the given search string.
|
|
||||||
*
|
|
||||||
* @param queryString the search string
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@GET
|
|
||||||
@Path("users")
|
|
||||||
@StatusCodes({
|
|
||||||
@ResponseCode(code = 200, condition = "success"),
|
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
|
||||||
})
|
|
||||||
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
|
|
||||||
public SearchResults searchUsers(@QueryParam("query") String queryString)
|
|
||||||
{
|
|
||||||
return userSearchHandler.search(queryString,
|
|
||||||
new Function<User, SearchResult>()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public SearchResult apply(User user)
|
|
||||||
{
|
|
||||||
StringBuilder label = new StringBuilder(user.getName());
|
|
||||||
|
|
||||||
label.append(" (").append(user.getDisplayName()).append(")");
|
|
||||||
|
|
||||||
return new SearchResult(user.getName(), label.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final SearchHandler<Group> groupSearchHandler;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final SearchHandler<User> userSearchHandler;
|
|
||||||
}
|
|
||||||
@@ -4,13 +4,15 @@ import de.otto.edison.hal.HalRepresentation;
|
|||||||
import sonia.scm.Manager;
|
import sonia.scm.Manager;
|
||||||
import sonia.scm.ModelObject;
|
import sonia.scm.ModelObject;
|
||||||
import sonia.scm.PageResult;
|
import sonia.scm.PageResult;
|
||||||
import sonia.scm.api.rest.resources.AbstractManagerResource;
|
import sonia.scm.util.AssertUtil;
|
||||||
|
import sonia.scm.util.Comparables;
|
||||||
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
import javax.ws.rs.core.GenericEntity;
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Collection;
|
import java.util.Comparator;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
|
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
|
||||||
@@ -27,21 +29,46 @@ import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
|
@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
|
||||||
class CollectionResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
class CollectionResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||||
DTO extends HalRepresentation> extends AbstractManagerResource<MODEL_OBJECT> {
|
DTO extends HalRepresentation>{
|
||||||
|
|
||||||
|
protected final Manager<MODEL_OBJECT> manager;
|
||||||
|
protected final Class<MODEL_OBJECT> type;
|
||||||
|
|
||||||
CollectionResourceManagerAdapter(Manager<MODEL_OBJECT> manager, Class<MODEL_OBJECT> type) {
|
CollectionResourceManagerAdapter(Manager<MODEL_OBJECT> manager, Class<MODEL_OBJECT> type) {
|
||||||
super(manager, type);
|
this.manager = manager;
|
||||||
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads all model objects in a paged way, maps them using the given function and returns a corresponding http response.
|
* Reads all model objects in a paged way, maps them using the given function and returns a corresponding http response.
|
||||||
* This handles all corner cases, eg. missing privileges.
|
* This handles all corner cases, eg. missing privileges.
|
||||||
*/
|
*/
|
||||||
public Response getAll(int page, int pageSize, String sortBy, boolean desc, Function<PageResult<MODEL_OBJECT>, CollectionDto> mapToDto) {
|
public Response getAll(int page, int pageSize, Predicate<MODEL_OBJECT> filter, String sortBy, boolean desc, Function<PageResult<MODEL_OBJECT>, CollectionDto> mapToDto) {
|
||||||
PageResult<MODEL_OBJECT> pageResult = fetchPage(sortBy, desc, page, pageSize);
|
PageResult<MODEL_OBJECT> pageResult = fetchPage(filter, sortBy, desc, page, pageSize);
|
||||||
return Response.ok(mapToDto.apply(pageResult)).build();
|
return Response.ok(mapToDto.apply(pageResult)).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PageResult<MODEL_OBJECT> fetchPage(Predicate<MODEL_OBJECT> filter, String sortBy, boolean desc, int pageNumber,
|
||||||
|
int pageSize) {
|
||||||
|
AssertUtil.assertPositive(pageNumber);
|
||||||
|
AssertUtil.assertPositive(pageSize);
|
||||||
|
|
||||||
|
if (Util.isEmpty(sortBy)) {
|
||||||
|
// replace with something useful
|
||||||
|
sortBy = "id";
|
||||||
|
}
|
||||||
|
|
||||||
|
return manager.getPage(filter, createComparator(sortBy, desc), pageNumber, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Comparator<MODEL_OBJECT> createComparator(String sortBy, boolean desc) {
|
||||||
|
Comparator<MODEL_OBJECT> comparator = Comparables.comparator(type, sortBy);
|
||||||
|
if (desc) {
|
||||||
|
comparator = comparator.reversed();
|
||||||
|
}
|
||||||
|
return comparator;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a model object for the given dto and returns a corresponding http response.
|
* Creates a model object for the given dto and returns a corresponding http response.
|
||||||
* This handles all corner cases, eg. no conflicts or missing privileges.
|
* This handles all corner cases, eg. no conflicts or missing privileges.
|
||||||
@@ -55,18 +82,7 @@ class CollectionResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
|||||||
return Response.created(URI.create(uriCreator.apply(created))).build();
|
return Response.created(URI.create(uriCreator.apply(created))).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected GenericEntity<Collection<MODEL_OBJECT>> createGenericEntity(Collection<MODEL_OBJECT> modelObjects) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getId(MODEL_OBJECT item) {
|
protected String getId(MODEL_OBJECT item) {
|
||||||
return item.getId();
|
return item.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getPathPart() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
|||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
import sonia.scm.group.Group;
|
import sonia.scm.group.Group;
|
||||||
import sonia.scm.group.GroupManager;
|
import sonia.scm.group.GroupManager;
|
||||||
|
import sonia.scm.search.SearchRequest;
|
||||||
|
import sonia.scm.search.SearchUtil;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -19,6 +21,9 @@ import javax.ws.rs.Path;
|
|||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||||
|
|
||||||
|
|
||||||
public class GroupCollectionResource {
|
public class GroupCollectionResource {
|
||||||
@@ -63,8 +68,10 @@ public class GroupCollectionResource {
|
|||||||
@DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
|
@DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
|
||||||
@QueryParam("sortBy") String sortBy,
|
@QueryParam("sortBy") String sortBy,
|
||||||
@DefaultValue("false")
|
@DefaultValue("false")
|
||||||
@QueryParam("desc") boolean desc) {
|
@QueryParam("desc") boolean desc,
|
||||||
return adapter.getAll(page, pageSize, sortBy, desc,
|
@DefaultValue("") @QueryParam("q") String search
|
||||||
|
) {
|
||||||
|
return adapter.getAll(page, pageSize, createSearchPredicate(search), sortBy, desc,
|
||||||
pageResult -> groupCollectionToDtoMapper.map(page, pageSize, pageResult));
|
pageResult -> groupCollectionToDtoMapper.map(page, pageSize, pageResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,4 +97,12 @@ public class GroupCollectionResource {
|
|||||||
() -> dtoToGroupMapper.map(group),
|
() -> dtoToGroupMapper.map(group),
|
||||||
g -> resourceLinks.group().self(g.getName()));
|
g -> resourceLinks.group().self(g.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Predicate<Group> createSearchPredicate(String search) {
|
||||||
|
if (isNullOrEmpty(search)) {
|
||||||
|
return group -> true;
|
||||||
|
}
|
||||||
|
SearchRequest searchRequest = new SearchRequest(search, true);
|
||||||
|
return group -> SearchUtil.matchesOne(searchRequest, group.getName(), group.getDescription());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ class IdResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response getAll(int page, int pageSize, String sortBy, boolean desc, Function<PageResult<MODEL_OBJECT>, CollectionDto> mapToDto) {
|
public Response getAll(int page, int pageSize, Predicate<MODEL_OBJECT> filter, String sortBy, boolean desc, Function<PageResult<MODEL_OBJECT>, CollectionDto> mapToDto) {
|
||||||
return collectionAdapter.getAll(page, pageSize, sortBy, desc, mapToDto);
|
return collectionAdapter.getAll(page, pageSize, filter, sortBy, desc, mapToDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) {
|
public Response create(DTO dto, Supplier<MODEL_OBJECT> modelObjectSupplier, Function<MODEL_OBJECT, String> uriCreator) {
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import org.apache.shiro.SecurityUtils;
|
|||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryManager;
|
import sonia.scm.repository.RepositoryManager;
|
||||||
import sonia.scm.repository.RepositoryPermission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
|
import sonia.scm.search.SearchRequest;
|
||||||
|
import sonia.scm.search.SearchUtil;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
@@ -23,6 +25,9 @@ import javax.ws.rs.Produces;
|
|||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
|
|
||||||
public class RepositoryCollectionResource {
|
public class RepositoryCollectionResource {
|
||||||
@@ -65,8 +70,10 @@ public class RepositoryCollectionResource {
|
|||||||
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
|
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
|
||||||
@DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
|
@DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
|
||||||
@QueryParam("sortBy") String sortBy,
|
@QueryParam("sortBy") String sortBy,
|
||||||
@DefaultValue("false") @QueryParam("desc") boolean desc) {
|
@DefaultValue("false") @QueryParam("desc") boolean desc,
|
||||||
return adapter.getAll(page, pageSize, sortBy, desc,
|
@DefaultValue("") @QueryParam("q") String search
|
||||||
|
) {
|
||||||
|
return adapter.getAll(page, pageSize, createSearchPredicate(search), sortBy, desc,
|
||||||
pageResult -> repositoryCollectionToDtoMapper.map(page, pageSize, pageResult));
|
pageResult -> repositoryCollectionToDtoMapper.map(page, pageSize, pageResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,4 +113,12 @@ public class RepositoryCollectionResource {
|
|||||||
private String currentUser() {
|
private String currentUser() {
|
||||||
return SecurityUtils.getSubject().getPrincipals().oneByType(User.class).getName();
|
return SecurityUtils.getSubject().getPrincipals().oneByType(User.class).getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Predicate<Repository> createSearchPredicate(String search) {
|
||||||
|
if (isNullOrEmpty(search)) {
|
||||||
|
return user -> true;
|
||||||
|
}
|
||||||
|
SearchRequest searchRequest = new SearchRequest(search, true);
|
||||||
|
return repository -> SearchUtil.matchesOne(searchRequest, repository.getName(), repository.getNamespace(), repository.getDescription());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,8 @@ import sonia.scm.ConcurrentModificationException;
|
|||||||
import sonia.scm.Manager;
|
import sonia.scm.Manager;
|
||||||
import sonia.scm.ModelObject;
|
import sonia.scm.ModelObject;
|
||||||
import sonia.scm.NotFoundException;
|
import sonia.scm.NotFoundException;
|
||||||
import sonia.scm.api.rest.resources.AbstractManagerResource;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.GenericEntity;
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
@@ -29,10 +26,11 @@ import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
|
@SuppressWarnings("squid:S00119") // "MODEL_OBJECT" is much more meaningful than "M", right?
|
||||||
class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
||||||
DTO extends HalRepresentation> extends AbstractManagerResource<MODEL_OBJECT> {
|
DTO extends HalRepresentation> {
|
||||||
|
|
||||||
private final Function<Throwable, Optional<Response>> errorHandler;
|
private final Function<Throwable, Optional<Response>> errorHandler;
|
||||||
private final Class<MODEL_OBJECT> type;
|
protected final Manager<MODEL_OBJECT> manager;
|
||||||
|
protected final Class<MODEL_OBJECT> type;
|
||||||
|
|
||||||
SingleResourceManagerAdapter(Manager<MODEL_OBJECT> manager, Class<MODEL_OBJECT> type) {
|
SingleResourceManagerAdapter(Manager<MODEL_OBJECT> manager, Class<MODEL_OBJECT> type) {
|
||||||
this(manager, type, e -> Optional.empty());
|
this(manager, type, e -> Optional.empty());
|
||||||
@@ -42,7 +40,7 @@ class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
|||||||
Manager<MODEL_OBJECT> manager,
|
Manager<MODEL_OBJECT> manager,
|
||||||
Class<MODEL_OBJECT> type,
|
Class<MODEL_OBJECT> type,
|
||||||
Function<Throwable, Optional<Response>> errorHandler) {
|
Function<Throwable, Optional<Response>> errorHandler) {
|
||||||
super(manager, type);
|
this.manager = manager;
|
||||||
this.errorHandler = errorHandler;
|
this.errorHandler = errorHandler;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
@@ -72,7 +70,16 @@ class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
|||||||
else if (modelObjectWasModifiedConcurrently(existingModelObject, changedModelObject)) {
|
else if (modelObjectWasModifiedConcurrently(existingModelObject, changedModelObject)) {
|
||||||
throw new ConcurrentModificationException(type, keyExtractor.apply(existingModelObject));
|
throw new ConcurrentModificationException(type, keyExtractor.apply(existingModelObject));
|
||||||
}
|
}
|
||||||
return update(getId(existingModelObject), changedModelObject);
|
return update(changedModelObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response update(MODEL_OBJECT item) {
|
||||||
|
try {
|
||||||
|
manager.modify(item);
|
||||||
|
return Response.noContent().build();
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
return createErrorResponse(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean modelObjectWasModifiedConcurrently(MODEL_OBJECT existing, MODEL_OBJECT updated) {
|
private boolean modelObjectWasModifiedConcurrently(MODEL_OBJECT existing, MODEL_OBJECT updated) {
|
||||||
@@ -89,23 +96,27 @@ class SingleResourceManagerAdapter<MODEL_OBJECT extends ModelObject,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public Response delete(String name) {
|
||||||
protected Response createErrorResponse(Throwable throwable) {
|
MODEL_OBJECT item = manager.get(name);
|
||||||
return errorHandler.apply(throwable).orElse(super.createErrorResponse(throwable));
|
|
||||||
|
if (item != null) {
|
||||||
|
try {
|
||||||
|
manager.delete(item);
|
||||||
|
return Response.noContent().build();
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
return createErrorResponse(ex);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Response.noContent().build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private Response createErrorResponse(RuntimeException throwable) {
|
||||||
protected GenericEntity<Collection<MODEL_OBJECT>> createGenericEntity(Collection<MODEL_OBJECT> modelObjects) {
|
return errorHandler.apply(throwable)
|
||||||
throw new UnsupportedOperationException();
|
.orElseThrow(() -> throwable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getId(MODEL_OBJECT item) {
|
protected String getId(MODEL_OBJECT item) {
|
||||||
return item.getId();
|
return item.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getPathPart() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
|
|||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
import org.apache.shiro.authc.credential.PasswordService;
|
import org.apache.shiro.authc.credential.PasswordService;
|
||||||
|
import sonia.scm.search.SearchRequest;
|
||||||
|
import sonia.scm.search.SearchUtil;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserManager;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
@@ -20,6 +22,9 @@ import javax.ws.rs.Path;
|
|||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||||
|
|
||||||
public class UserCollectionResource {
|
public class UserCollectionResource {
|
||||||
|
|
||||||
@@ -65,8 +70,10 @@ public class UserCollectionResource {
|
|||||||
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
|
public Response getAll(@DefaultValue("0") @QueryParam("page") int page,
|
||||||
@DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
|
@DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize,
|
||||||
@QueryParam("sortBy") String sortBy,
|
@QueryParam("sortBy") String sortBy,
|
||||||
@DefaultValue("false") @QueryParam("desc") boolean desc) {
|
@DefaultValue("false") @QueryParam("desc") boolean desc,
|
||||||
return adapter.getAll(page, pageSize, sortBy, desc,
|
@DefaultValue("") @QueryParam("q") String search
|
||||||
|
) {
|
||||||
|
return adapter.getAll(page, pageSize, createSearchPredicate(search), sortBy, desc,
|
||||||
pageResult -> userCollectionToDtoMapper.map(page, pageSize, pageResult));
|
pageResult -> userCollectionToDtoMapper.map(page, pageSize, pageResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,4 +100,12 @@ public class UserCollectionResource {
|
|||||||
public Response create(@Valid UserDto user) {
|
public Response create(@Valid UserDto user) {
|
||||||
return adapter.create(user, () -> dtoToUserMapper.map(user, passwordService.encryptPassword(user.getPassword())), u -> resourceLinks.user().self(u.getName()));
|
return adapter.create(user, () -> dtoToUserMapper.map(user, passwordService.encryptPassword(user.getPassword())), u -> resourceLinks.user().self(u.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Predicate<User> createSearchPredicate(String search) {
|
||||||
|
if (isNullOrEmpty(search)) {
|
||||||
|
return user -> true;
|
||||||
|
}
|
||||||
|
SearchRequest searchRequest = new SearchRequest(search, true);
|
||||||
|
return user -> SearchUtil.matchesOne(searchRequest, user.getName(), user.getDisplayName(), user.getMail());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ import java.util.Collections;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -250,7 +251,7 @@ public class DefaultGroupManager extends AbstractGroupManager
|
|||||||
@Override
|
@Override
|
||||||
public Collection<Group> getAll()
|
public Collection<Group> getAll()
|
||||||
{
|
{
|
||||||
return getAll(null);
|
return getAll(group -> true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -262,14 +263,14 @@ public class DefaultGroupManager extends AbstractGroupManager
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Collection<Group> getAll(Comparator<Group> comparator)
|
public Collection<Group> getAll(Predicate<Group> filter, Comparator<Group> comparator)
|
||||||
{
|
{
|
||||||
List<Group> groups = new ArrayList<>();
|
List<Group> groups = new ArrayList<>();
|
||||||
|
|
||||||
PermissionActionCheck<Group> check = GroupPermissions.read();
|
PermissionActionCheck<Group> check = GroupPermissions.read();
|
||||||
for (Group group : groupDAO.getAll())
|
for (Group group : groupDAO.getAll())
|
||||||
{
|
{
|
||||||
if (check.isPermitted(group)) {
|
if (filter.test(group) && check.isPermitted(group)) {
|
||||||
groups.add(group.clone());
|
groups.add(group.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ import java.util.Set;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import static sonia.scm.AlreadyExistsException.alreadyExists;
|
import static sonia.scm.AlreadyExistsException.alreadyExists;
|
||||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||||
@@ -253,13 +254,14 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Repository> getAll(Comparator<Repository> comparator) {
|
public Collection<Repository> getAll(Predicate<Repository> filter, Comparator<Repository> comparator) {
|
||||||
List<Repository> repositories = Lists.newArrayList();
|
List<Repository> repositories = Lists.newArrayList();
|
||||||
|
|
||||||
PermissionActionCheck<Repository> check = RepositoryPermissions.read();
|
PermissionActionCheck<Repository> check = RepositoryPermissions.read();
|
||||||
|
|
||||||
for (Repository repository : repositoryDAO.getAll()) {
|
for (Repository repository : repositoryDAO.getAll()) {
|
||||||
if (handlerMap.containsKey(repository.getType())
|
if (handlerMap.containsKey(repository.getType())
|
||||||
|
&& filter.test(repository)
|
||||||
&& check.isPermitted(repository)) {
|
&& check.isPermitted(repository)) {
|
||||||
Repository r = repository.clone();
|
Repository r = repository.clone();
|
||||||
|
|
||||||
@@ -276,7 +278,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Repository> getAll() {
|
public Collection<Repository> getAll() {
|
||||||
return getAll(null);
|
return getAll(repository -> true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,221 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.search;
|
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
|
||||||
import com.google.common.collect.Collections2;
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
|
|
||||||
import org.apache.shiro.SecurityUtils;
|
|
||||||
import org.apache.shiro.subject.Subject;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import sonia.scm.cache.Cache;
|
|
||||||
import sonia.scm.security.ScmSecurityException;
|
|
||||||
import sonia.scm.util.Util;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import javax.ws.rs.WebApplicationException;
|
|
||||||
import javax.ws.rs.core.Response.Status;
|
|
||||||
import sonia.scm.security.Role;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*
|
|
||||||
* @param <T>
|
|
||||||
*/
|
|
||||||
public class SearchHandler<T>
|
|
||||||
{
|
|
||||||
|
|
||||||
/** the logger for SearchHandler */
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(SearchHandler.class);
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param securityContextProvider
|
|
||||||
* @param cache
|
|
||||||
* @param searchable
|
|
||||||
*/
|
|
||||||
public SearchHandler(Cache<String, SearchResults> cache,
|
|
||||||
Searchable<T> searchable)
|
|
||||||
{
|
|
||||||
|
|
||||||
this.cache = cache;
|
|
||||||
this.searchable = searchable;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public void clearCache()
|
|
||||||
{
|
|
||||||
this.cache.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param queryString
|
|
||||||
* @param function
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public SearchResults search(String queryString,
|
|
||||||
Function<T, SearchResult> function)
|
|
||||||
{
|
|
||||||
Subject subject = SecurityUtils.getSubject();
|
|
||||||
|
|
||||||
if (!subject.hasRole(Role.USER))
|
|
||||||
{
|
|
||||||
throw new ScmSecurityException("Authentication is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Util.isEmpty(queryString))
|
|
||||||
{
|
|
||||||
throw new WebApplicationException(Status.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchResults result = cache.get(queryString);
|
|
||||||
|
|
||||||
if (result == null)
|
|
||||||
{
|
|
||||||
SearchRequest request = new SearchRequest(queryString, ignoreCase);
|
|
||||||
|
|
||||||
request.setMaxResults(maxResults);
|
|
||||||
|
|
||||||
Collection<T> users = searchable.search(request);
|
|
||||||
|
|
||||||
result = new SearchResults();
|
|
||||||
|
|
||||||
if (Util.isNotEmpty(users))
|
|
||||||
{
|
|
||||||
Collection<SearchResult> resultCollection =
|
|
||||||
Collections2.transform(users, function);
|
|
||||||
|
|
||||||
result.setSuccess(true);
|
|
||||||
|
|
||||||
// create a copy of the result collection to reduce memory
|
|
||||||
// use ArrayList instead of ImmutableList for copy,
|
|
||||||
// because the list must be mutable for decorators
|
|
||||||
result.setResults(Lists.newArrayList(resultCollection));
|
|
||||||
cache.put(queryString, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug("return searchresults for {} from cache", queryString);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public int getMaxResults()
|
|
||||||
{
|
|
||||||
return maxResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public boolean isIgnoreCase()
|
|
||||||
{
|
|
||||||
return ignoreCase;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param ignoreCase
|
|
||||||
*/
|
|
||||||
public void setIgnoreCase(boolean ignoreCase)
|
|
||||||
{
|
|
||||||
this.ignoreCase = ignoreCase;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param maxResults
|
|
||||||
*/
|
|
||||||
public void setMaxResults(int maxResults)
|
|
||||||
{
|
|
||||||
this.maxResults = maxResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
protected Cache<String, SearchResults> cache;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
protected Searchable<T> searchable;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private int maxResults = 5;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private boolean ignoreCase = true;
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.search;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
public class SearchResult
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public SearchResult() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param value
|
|
||||||
* @param label
|
|
||||||
*/
|
|
||||||
public SearchResult(String value, String label)
|
|
||||||
{
|
|
||||||
this.value = value;
|
|
||||||
this.label = label;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String getLabel()
|
|
||||||
{
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public String getValue()
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param label
|
|
||||||
*/
|
|
||||||
public void setLabel(String label)
|
|
||||||
{
|
|
||||||
this.label = label;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param value
|
|
||||||
*/
|
|
||||||
public void setValue(String value)
|
|
||||||
{
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private String label;
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private String value;
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2010, Sebastian Sdorra
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* http://bitbucket.org/sdorra/scm-manager
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
package sonia.scm.search;
|
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
|
||||||
import sonia.scm.api.rest.RestActionResult;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
|
||||||
*/
|
|
||||||
@XmlRootElement(name = "search-results")
|
|
||||||
public class SearchResults extends RestActionResult
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public SearchResults() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param success
|
|
||||||
*/
|
|
||||||
public SearchResults(boolean success)
|
|
||||||
{
|
|
||||||
super(success);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param results
|
|
||||||
*/
|
|
||||||
public SearchResults(Collection<SearchResult> results)
|
|
||||||
{
|
|
||||||
super(true);
|
|
||||||
this.results = results;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Collection<SearchResult> getResults()
|
|
||||||
{
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- set methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param results
|
|
||||||
*/
|
|
||||||
public void setResults(Collection<SearchResult> results)
|
|
||||||
{
|
|
||||||
this.results = results;
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private Collection<SearchResult> results;
|
|
||||||
}
|
|
||||||
@@ -62,6 +62,7 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -280,7 +281,7 @@ public class DefaultUserManager extends AbstractUserManager
|
|||||||
@Override
|
@Override
|
||||||
public Collection<User> getAll()
|
public Collection<User> getAll()
|
||||||
{
|
{
|
||||||
return getAll(null);
|
return getAll(user -> true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -292,13 +293,13 @@ public class DefaultUserManager extends AbstractUserManager
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Collection<User> getAll(Comparator<User> comparator)
|
public Collection<User> getAll(Predicate<User> filter, Comparator<User> comparator)
|
||||||
{
|
{
|
||||||
List<User> users = new ArrayList<>();
|
List<User> users = new ArrayList<>();
|
||||||
|
|
||||||
PermissionActionCheck<User> check = UserPermissions.read();
|
PermissionActionCheck<User> check = UserPermissions.read();
|
||||||
for (User user : userDAO.getAll()) {
|
for (User user : userDAO.getAll()) {
|
||||||
if (check.isPermitted(user)) {
|
if (filter.test(user) && check.isPermitted(user)) {
|
||||||
users.add(user.clone());
|
users.add(user.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
package sonia.scm.api.rest.resources;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.Captor;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
|
||||||
import sonia.scm.Manager;
|
|
||||||
import sonia.scm.ModelObject;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.GenericEntity;
|
|
||||||
import javax.ws.rs.core.Request;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
|
||||||
public class AbstractManagerResourceTest {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private Manager<Simple> manager;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private Request request;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private UriInfo uriInfo;
|
|
||||||
|
|
||||||
@Captor
|
|
||||||
private ArgumentCaptor<Comparator<Simple>> comparatorCaptor;
|
|
||||||
|
|
||||||
private AbstractManagerResource<Simple> abstractManagerResource;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void captureComparator() {
|
|
||||||
when(manager.getAll(comparatorCaptor.capture(), eq(0), eq(1))).thenReturn(emptyList());
|
|
||||||
abstractManagerResource = new SimpleManagerResource();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldAcceptDefaultSortByParameter() {
|
|
||||||
abstractManagerResource.getAll(request, 0, 1, null, true);
|
|
||||||
|
|
||||||
Comparator<Simple> comparator = comparatorCaptor.getValue();
|
|
||||||
assertTrue(comparator.compare(new Simple("1", null), new Simple("2", null)) > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldAcceptValidSortByParameter() {
|
|
||||||
abstractManagerResource.getAll(request, 0, 1, "data", true);
|
|
||||||
|
|
||||||
Comparator<Simple> comparator = comparatorCaptor.getValue();
|
|
||||||
assertTrue(comparator.compare(new Simple("", "1"), new Simple("", "2")) > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
|
||||||
public void shouldFailForIllegalSortByParameter() {
|
|
||||||
abstractManagerResource.getAll(request, 0, 1, "x", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLocation() throws URISyntaxException {
|
|
||||||
URI uri = location("special-item");
|
|
||||||
assertEquals(new URI("https://scm.scm-manager.org/simple/special-item"), uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLocationWithSpaces() throws URISyntaxException {
|
|
||||||
URI uri = location("Scm Special Group");
|
|
||||||
assertEquals(new URI("https://scm.scm-manager.org/simple/Scm%20Special%20Group"), uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
private URI location(String id) throws URISyntaxException {
|
|
||||||
URI base = new URI("https://scm.scm-manager.org/");
|
|
||||||
when(uriInfo.getAbsolutePath()).thenReturn(base);
|
|
||||||
|
|
||||||
return abstractManagerResource.location(uriInfo, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SimpleManagerResource extends AbstractManagerResource<Simple> {
|
|
||||||
|
|
||||||
{
|
|
||||||
disableCache = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SimpleManagerResource() {
|
|
||||||
super(AbstractManagerResourceTest.this.manager, Simple.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected GenericEntity<Collection<Simple>> createGenericEntity(Collection<Simple> items) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getId(Simple item) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getPathPart() {
|
|
||||||
return "simple";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Simple implements ModelObject {
|
|
||||||
|
|
||||||
private String id;
|
|
||||||
private String data;
|
|
||||||
|
|
||||||
Simple(String id, String data) {
|
|
||||||
this.id = id;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getData() {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setLastModified(Long timestamp) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long getCreationDate() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setCreationDate(Long timestamp) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long getLastModified() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getType() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public boolean isValid() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package sonia.scm.api.rest.resources;
|
||||||
|
|
||||||
|
import sonia.scm.ModelObject;
|
||||||
|
|
||||||
|
public class Simple implements ModelObject {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String data;
|
||||||
|
|
||||||
|
public Simple(String id, String data) {
|
||||||
|
this.id = id;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLastModified(Long timestamp) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getCreationDate() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCreationDate(Long timestamp) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getLastModified() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean isValid() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
import sonia.scm.Manager;
|
||||||
|
import sonia.scm.api.rest.resources.Simple;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class CollectionResourceManagerAdapterTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Manager<Simple> manager;
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<Comparator<Simple>> comparatorCaptor;
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<Predicate<Simple>> filterCaptor;
|
||||||
|
|
||||||
|
private CollectionResourceManagerAdapter<Simple, HalRepresentation> abstractManagerResource;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void captureComparator() {
|
||||||
|
when(manager.getPage(filterCaptor.capture(), comparatorCaptor.capture(), eq(0), eq(1))).thenReturn(null);
|
||||||
|
abstractManagerResource = new SimpleManagerResource();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldAcceptDefaultSortByParameter() {
|
||||||
|
abstractManagerResource.getAll(0, 1, x -> true, null, true, r -> null);
|
||||||
|
|
||||||
|
Comparator<Simple> comparator = comparatorCaptor.getValue();
|
||||||
|
assertTrue(comparator.compare(new Simple("1", null), new Simple("2", null)) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldAcceptValidSortByParameter() {
|
||||||
|
abstractManagerResource.getAll(0, 1, x -> true, "data", true, r -> null);
|
||||||
|
|
||||||
|
Comparator<Simple> comparator = comparatorCaptor.getValue();
|
||||||
|
assertTrue(comparator.compare(new Simple("", "1"), new Simple("", "2")) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void shouldFailForIllegalSortByParameter() {
|
||||||
|
abstractManagerResource.getAll(0, 1, x -> true, "x", true, r -> null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SimpleManagerResource extends CollectionResourceManagerAdapter<Simple, HalRepresentation> {
|
||||||
|
private SimpleManagerResource() {
|
||||||
|
super(CollectionResourceManagerAdapterTest.this.manager, Simple.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import org.junit.Before;
|
|||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import sonia.scm.PageResult;
|
import sonia.scm.PageResult;
|
||||||
@@ -30,9 +31,11 @@ import java.net.URISyntaxException;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
@@ -67,8 +70,10 @@ public class GroupRootResourceTest {
|
|||||||
@InjectMocks
|
@InjectMocks
|
||||||
private PermissionCollectionToDtoMapper permissionCollectionToDtoMapper;
|
private PermissionCollectionToDtoMapper permissionCollectionToDtoMapper;
|
||||||
|
|
||||||
|
@Captor
|
||||||
private ArgumentCaptor<Group> groupCaptor = ArgumentCaptor.forClass(Group.class);
|
private ArgumentCaptor<Group> groupCaptor;
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<Predicate<Group>> filterCaptor;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void prepareEnvironment() {
|
public void prepareEnvironment() {
|
||||||
@@ -77,7 +82,7 @@ public class GroupRootResourceTest {
|
|||||||
doNothing().when(groupManager).modify(groupCaptor.capture());
|
doNothing().when(groupManager).modify(groupCaptor.capture());
|
||||||
|
|
||||||
Group group = createDummyGroup();
|
Group group = createDummyGroup();
|
||||||
when(groupManager.getPage(any(), eq(0), eq(10))).thenReturn(new PageResult<>(singletonList(group), 1));
|
when(groupManager.getPage(filterCaptor.capture(), any(), eq(0), eq(10))).thenReturn(new PageResult<>(singletonList(group), 1));
|
||||||
when(groupManager.get("admin")).thenReturn(group);
|
when(groupManager.get("admin")).thenReturn(group);
|
||||||
|
|
||||||
GroupCollectionToDtoMapper groupCollectionToDtoMapper = new GroupCollectionToDtoMapper(groupToDtoMapper, resourceLinks);
|
GroupCollectionToDtoMapper groupCollectionToDtoMapper = new GroupCollectionToDtoMapper(groupToDtoMapper, resourceLinks);
|
||||||
@@ -317,6 +322,23 @@ public class GroupRootResourceTest {
|
|||||||
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/groups/admin\"}"));
|
assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/groups/admin\"}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateFilterForSearch() throws URISyntaxException {
|
||||||
|
MockHttpRequest request = MockHttpRequest.get("/" + GroupRootResource.GROUPS_PATH_V2 + "?q=One");
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||||
|
Group group = new Group("xml", "someone");
|
||||||
|
assertTrue(filterCaptor.getValue().test(group));
|
||||||
|
group.setName("nothing");
|
||||||
|
group.setDescription("Someone");
|
||||||
|
assertTrue(filterCaptor.getValue().test(group));
|
||||||
|
group.setDescription("Nobody");
|
||||||
|
assertFalse(filterCaptor.getValue().test(group));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldGetPermissionLink() throws URISyntaxException, UnsupportedEncodingException {
|
public void shouldGetPermissionLink() throws URISyntaxException, UnsupportedEncodingException {
|
||||||
MockHttpRequest request = MockHttpRequest.get("/" + GroupRootResource.GROUPS_PATH_V2 + "admin");
|
MockHttpRequest request = MockHttpRequest.get("/" + GroupRootResource.GROUPS_PATH_V2 + "admin");
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.junit.Before;
|
|||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import sonia.scm.PageResult;
|
import sonia.scm.PageResult;
|
||||||
@@ -31,6 +32,7 @@ import java.io.UnsupportedEncodingException;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.stream.Stream.of;
|
import static java.util.stream.Stream.of;
|
||||||
@@ -42,6 +44,7 @@ import static javax.servlet.http.HttpServletResponse.SC_OK;
|
|||||||
import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
|
import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyObject;
|
import static org.mockito.ArgumentMatchers.anyObject;
|
||||||
@@ -78,6 +81,8 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
@Mock
|
@Mock
|
||||||
private ScmPathInfo uriInfo;
|
private ScmPathInfo uriInfo;
|
||||||
|
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<Predicate<Repository>> filterCaptor;
|
||||||
|
|
||||||
private final URI baseUri = URI.create("/");
|
private final URI baseUri = URI.create("/");
|
||||||
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||||
@@ -150,7 +155,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldGetAll() throws URISyntaxException, UnsupportedEncodingException {
|
public void shouldGetAll() throws URISyntaxException, UnsupportedEncodingException {
|
||||||
PageResult<Repository> singletonPageResult = createSingletonPageResult(mockRepository("space", "repo"));
|
PageResult<Repository> singletonPageResult = createSingletonPageResult(mockRepository("space", "repo"));
|
||||||
when(repositoryManager.getPage(any(), eq(0), eq(10))).thenReturn(singletonPageResult);
|
when(repositoryManager.getPage(any(), any(), eq(0), eq(10))).thenReturn(singletonPageResult);
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2);
|
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2);
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
@@ -161,6 +166,22 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
assertTrue(response.getContentAsString().contains("\"name\":\"repo\""));
|
assertTrue(response.getContentAsString().contains("\"name\":\"repo\""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateFilterForSearch() throws URISyntaxException {
|
||||||
|
PageResult<Repository> singletonPageResult = createSingletonPageResult(mockRepository("space", "repo"));
|
||||||
|
when(repositoryManager.getPage(filterCaptor.capture(), any(), eq(0), eq(10))).thenReturn(singletonPageResult);
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "?q=Rep");
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(SC_OK, response.getStatus());
|
||||||
|
assertTrue(filterCaptor.getValue().test(new Repository("x", "git", "all_repos", "x")));
|
||||||
|
assertTrue(filterCaptor.getValue().test(new Repository("x", "git", "x", "repository")));
|
||||||
|
assertFalse(filterCaptor.getValue().test(new Repository("rep", "rep", "x", "x")));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldHandleUpdateForNotExistingRepository() throws URISyntaxException, IOException {
|
public void shouldHandleUpdateForNotExistingRepository() throws URISyntaxException, IOException {
|
||||||
URL url = Resources.getResource("sonia/scm/api/v2/repository-test-update.json");
|
URL url = Resources.getResource("sonia/scm/api/v2/repository-test-update.json");
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.junit.Before;
|
|||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import sonia.scm.ContextEntry;
|
import sonia.scm.ContextEntry;
|
||||||
@@ -30,6 +31,7 @@ import java.net.URI;
|
|||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -72,7 +74,11 @@ public class UserRootResourceTest {
|
|||||||
@InjectMocks
|
@InjectMocks
|
||||||
private PermissionCollectionToDtoMapper permissionCollectionToDtoMapper;
|
private PermissionCollectionToDtoMapper permissionCollectionToDtoMapper;
|
||||||
|
|
||||||
private ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
|
@Captor
|
||||||
|
private ArgumentCaptor<User> userCaptor;
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<Predicate<User>> filterCaptor;
|
||||||
|
|
||||||
private User originalUser;
|
private User originalUser;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -333,7 +339,7 @@ public class UserRootResourceTest {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldCreatePageForOnePageOnly() throws URISyntaxException, UnsupportedEncodingException {
|
public void shouldCreatePageForOnePageOnly() throws URISyntaxException, UnsupportedEncodingException {
|
||||||
PageResult<User> singletonPageResult = createSingletonPageResult(1);
|
PageResult<User> singletonPageResult = createSingletonPageResult(1);
|
||||||
when(userManager.getPage(any(), eq(0), eq(10))).thenReturn(singletonPageResult);
|
when(userManager.getPage(any(), any(), eq(0), eq(10))).thenReturn(singletonPageResult);
|
||||||
MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2);
|
MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2);
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
@@ -349,7 +355,7 @@ public class UserRootResourceTest {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldCreatePageForMultiplePages() throws URISyntaxException, UnsupportedEncodingException {
|
public void shouldCreatePageForMultiplePages() throws URISyntaxException, UnsupportedEncodingException {
|
||||||
PageResult<User> singletonPageResult = createSingletonPageResult(3);
|
PageResult<User> singletonPageResult = createSingletonPageResult(3);
|
||||||
when(userManager.getPage(any(), eq(1), eq(1))).thenReturn(singletonPageResult);
|
when(userManager.getPage(any(), any(), eq(1), eq(1))).thenReturn(singletonPageResult);
|
||||||
MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2 + "?page=1&pageSize=1");
|
MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2 + "?page=1&pageSize=1");
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
@@ -364,6 +370,28 @@ public class UserRootResourceTest {
|
|||||||
assertTrue(response.getContentAsString().contains("\"last\":{\"href\":\"/v2/users/?page=2"));
|
assertTrue(response.getContentAsString().contains("\"last\":{\"href\":\"/v2/users/?page=2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateFilterForSearch() throws URISyntaxException {
|
||||||
|
PageResult<User> singletonPageResult = createSingletonPageResult(1);
|
||||||
|
when(userManager.getPage(filterCaptor.capture(), any(), eq(0), eq(10))).thenReturn(singletonPageResult);
|
||||||
|
MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2 + "?q=One");
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
|
||||||
|
User user = new User("Someone I know");
|
||||||
|
assertTrue(filterCaptor.getValue().test(user));
|
||||||
|
user.setName("nobody");
|
||||||
|
user.setDisplayName("Someone I know");
|
||||||
|
assertTrue(filterCaptor.getValue().test(user));
|
||||||
|
user.setDisplayName("nobody");
|
||||||
|
user.setMail("me@someone.com");
|
||||||
|
assertTrue(filterCaptor.getValue().test(user));
|
||||||
|
user.setMail("me@nowhere.com");
|
||||||
|
assertFalse(filterCaptor.getValue().test(user));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldGetPermissionLink() throws URISyntaxException, UnsupportedEncodingException {
|
public void shouldGetPermissionLink() throws URISyntaxException, UnsupportedEncodingException {
|
||||||
MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2 + "Neo");
|
MockHttpRequest request = MockHttpRequest.get("/" + UserRootResource.USERS_PATH_V2 + "Neo");
|
||||||
|
|||||||
Reference in New Issue
Block a user