From aee67b05bcf713145b76640e9903546bd19ab406 Mon Sep 17 00:00:00 2001
From: Andrey Popov
Date: Fri, 21 Oct 2022 13:52:06 +0300
Subject: [PATCH 1/2] feat: Provide web gui for {list, add, add, update,
remove} repository settings
---
src/main/java/com/artipie/front/Service.java | 82 +++--
.../com/artipie/front/rest/AuthService.java | 31 +-
.../com/artipie/front/rest/BaseService.java | 194 ++++++++----
.../artipie/front/rest/RepositoryName.java | 141 +++++++++
.../artipie/front/rest/RepositoryService.java | 96 +++++-
.../java/com/artipie/front/ui/PostSignIn.java | 24 +-
.../java/com/artipie/front/ui/RepoPage.java | 166 ----------
.../java/com/artipie/front/ui/UserPage.java | 38 ---
.../front/ui/repository/RepoAddConfig.java | 66 ++++
.../front/ui/repository/RepoAddInfo.java | 32 ++
.../artipie/front/ui/repository/RepoEdit.java | 96 ++++++
.../artipie/front/ui/repository/RepoList.java | 23 +-
.../front/ui/repository/RepoRemove.java | 46 +++
.../artipie/front/ui/repository/RepoSave.java | 50 +++
.../front/ui/repository/RepositoryInfo.java | 68 ++++
.../ui/repository/RepositoryTemplate.java | 74 +++++
.../ui/repository/flat/package-info.java | 10 -
.../ui/repository/org/RepoListByUser.java | 45 ---
.../front/ui/repository/org/package-info.java | 10 -
src/main/resources/html/base | 33 +-
src/main/resources/html/repo | 295 ------------------
src/main/resources/html/repository/add | 27 ++
src/main/resources/html/repository/add_config | 23 ++
.../html/{user => repository/add_info} | 24 +-
src/main/resources/html/repository/edit | 57 ++++
src/main/resources/html/repository/list | 30 +-
src/main/resources/html/repository/org/list | 29 --
src/main/resources/html/repository/result | 18 ++
src/main/resources/info/conda.info.html | 18 ++
src/main/resources/info/deb.info.html | 6 +
src/main/resources/info/docker.info.html | 8 +
src/main/resources/info/file-proxy.info.html | 0
src/main/resources/info/file.info.html | 9 +
src/main/resources/info/gem.info.html | 13 +
src/main/resources/info/go.info.html | 9 +
src/main/resources/info/helm.info.html | 9 +
src/main/resources/info/maven-group.info.html | 8 +
src/main/resources/info/maven-proxy.info.html | 18 ++
src/main/resources/info/maven.info.html | 31 ++
src/main/resources/info/npm-proxy.info.html | 0
src/main/resources/info/npm.info.html | 6 +
src/main/resources/info/nuget.info.html | 21 ++
src/main/resources/info/php-proxy.info.html | 0
src/main/resources/info/php.info.html | 26 ++
src/main/resources/info/pypi-proxy.info.html | 0
src/main/resources/info/pypi.info.html | 9 +
src/main/resources/info/rpm.info.html | 14 +
.../resources/template/default.template.yaml | 9 +
.../template/maven-group.template.yaml | 14 +
.../template/maven-proxy.template.yaml | 14 +
50 files changed, 1310 insertions(+), 760 deletions(-)
create mode 100644 src/main/java/com/artipie/front/rest/RepositoryName.java
delete mode 100644 src/main/java/com/artipie/front/ui/RepoPage.java
delete mode 100644 src/main/java/com/artipie/front/ui/UserPage.java
create mode 100644 src/main/java/com/artipie/front/ui/repository/RepoAddConfig.java
create mode 100644 src/main/java/com/artipie/front/ui/repository/RepoAddInfo.java
create mode 100644 src/main/java/com/artipie/front/ui/repository/RepoEdit.java
create mode 100644 src/main/java/com/artipie/front/ui/repository/RepoRemove.java
create mode 100644 src/main/java/com/artipie/front/ui/repository/RepoSave.java
create mode 100644 src/main/java/com/artipie/front/ui/repository/RepositoryInfo.java
create mode 100644 src/main/java/com/artipie/front/ui/repository/RepositoryTemplate.java
delete mode 100644 src/main/java/com/artipie/front/ui/repository/flat/package-info.java
delete mode 100644 src/main/java/com/artipie/front/ui/repository/org/RepoListByUser.java
delete mode 100644 src/main/java/com/artipie/front/ui/repository/org/package-info.java
delete mode 100644 src/main/resources/html/repo
create mode 100644 src/main/resources/html/repository/add
create mode 100644 src/main/resources/html/repository/add_config
rename src/main/resources/html/{user => repository/add_info} (71%)
create mode 100644 src/main/resources/html/repository/edit
delete mode 100644 src/main/resources/html/repository/org/list
create mode 100644 src/main/resources/html/repository/result
create mode 100644 src/main/resources/info/conda.info.html
create mode 100644 src/main/resources/info/deb.info.html
create mode 100644 src/main/resources/info/docker.info.html
create mode 100644 src/main/resources/info/file-proxy.info.html
create mode 100644 src/main/resources/info/file.info.html
create mode 100644 src/main/resources/info/gem.info.html
create mode 100644 src/main/resources/info/go.info.html
create mode 100644 src/main/resources/info/helm.info.html
create mode 100644 src/main/resources/info/maven-group.info.html
create mode 100644 src/main/resources/info/maven-proxy.info.html
create mode 100644 src/main/resources/info/maven.info.html
create mode 100644 src/main/resources/info/npm-proxy.info.html
create mode 100644 src/main/resources/info/npm.info.html
create mode 100644 src/main/resources/info/nuget.info.html
create mode 100644 src/main/resources/info/php-proxy.info.html
create mode 100644 src/main/resources/info/php.info.html
create mode 100644 src/main/resources/info/pypi-proxy.info.html
create mode 100644 src/main/resources/info/pypi.info.html
create mode 100644 src/main/resources/info/rpm.info.html
create mode 100644 src/main/resources/template/default.template.yaml
create mode 100644 src/main/resources/template/maven-group.template.yaml
create mode 100644 src/main/resources/template/maven-proxy.template.yaml
diff --git a/src/main/java/com/artipie/front/Service.java b/src/main/java/com/artipie/front/Service.java
index 276c62c..bf495c8 100644
--- a/src/main/java/com/artipie/front/Service.java
+++ b/src/main/java/com/artipie/front/Service.java
@@ -27,11 +27,15 @@
import com.artipie.front.settings.YamlRepoPermissions;
import com.artipie.front.ui.HbTemplateEngine;
import com.artipie.front.ui.PostSignIn;
-import com.artipie.front.ui.RepoPage;
import com.artipie.front.ui.SignInPage;
-import com.artipie.front.ui.UserPage;
+import com.artipie.front.ui.repository.RepoAddConfig;
+import com.artipie.front.ui.repository.RepoAddInfo;
+import com.artipie.front.ui.repository.RepoEdit;
import com.artipie.front.ui.repository.RepoList;
-import com.artipie.front.ui.repository.org.RepoListByUser;
+import com.artipie.front.ui.repository.RepoRemove;
+import com.artipie.front.ui.repository.RepoSave;
+import com.artipie.front.ui.repository.RepositoryInfo;
+import com.artipie.front.ui.repository.RepositoryTemplate;
import com.fasterxml.jackson.core.JsonParseException;
import com.jcabi.log.Logger;
import java.io.File;
@@ -290,36 +294,62 @@ void start(final int port) {
this.ignite.path(
"/dashboard",
() -> {
+ this.ignite.get(
+ "",
+ (req, res) -> {
+ res.redirect("/dashboard/repository/list");
+ return "Ok";
+ }
+ );
final ArtipieEndpoint endpoint = this.settings.artipieEnpoint();
final RepositoryService repository = new RepositoryService(endpoint);
+ final RepositoryInfo info = new RepositoryInfo();
+ final RepositoryTemplate template = new RepositoryTemplate();
this.ignite.path(
"/repository", () -> {
- this.ignite.get("/list", new RepoList(repository), engine);
- if ("org".equals(this.settings.layout())) {
- this.ignite.get("/list/:uname", new RepoListByUser(repository), engine);
+ final String layout = this.settings.layout();
+ this.ignite.get("/list", new RepoList(repository, layout), engine);
+ if ("flat".equals(layout)) {
+ this.ignite.get(
+ "/edit/:repo",
+ new RepoEdit(repository, layout, info),
+ engine
+ );
+ this.ignite.post(
+ "/update/:repo",
+ new RepoSave(repository, layout),
+ engine
+ );
+ this.ignite.post(
+ "/remove/:repo",
+ new RepoRemove(repository, layout),
+ engine
+ );
+ } else {
+ this.ignite.get(
+ "/edit/:user/:repo",
+ new RepoEdit(repository, layout, info),
+ engine
+ );
+ this.ignite.post(
+ "/update/:user/:repo",
+ new RepoSave(repository, layout),
+ engine
+ );
+ this.ignite.post(
+ "/remove/:user/:repo",
+ new RepoRemove(repository, layout),
+ engine
+ );
}
+ this.ignite.get("/add/info", new RepoAddInfo(), engine);
+ this.ignite.get(
+ "/add/config",
+ new RepoAddConfig(layout, info, template),
+ engine
+ );
}
);
- final RepoSettings stn = new RepoSettings(
- this.settings.layout(), this.settings.repoConfigsStorage()
- );
- this.ignite.get("", new UserPage(stn), engine);
- this.ignite.get(
- new RequestPath().with(Users.USER_PARAM).toString(), new UserPage(stn), engine
- );
- this.ignite.get(
- new RequestPath().with(Users.USER_PARAM)
- .with(Repositories.REPO_PARAM).toString(),
- new RepoPage.TemplateView(stn), engine
- );
- this.ignite.post(
- new RequestPath().with("api").with("repos").with(Users.USER_PARAM).toString(),
- new RepoPage.Post(stn)
- );
- this.ignite.get(
- new RequestPath().with("api").with("repos").with(Users.USER_PARAM).toString(),
- new RepoPage.Get()
- );
}
);
this.ignite.before(AuthFilters.AUTHENTICATE);
diff --git a/src/main/java/com/artipie/front/rest/AuthService.java b/src/main/java/com/artipie/front/rest/AuthService.java
index 24fc675..6b5eb8f 100644
--- a/src/main/java/com/artipie/front/rest/AuthService.java
+++ b/src/main/java/com/artipie/front/rest/AuthService.java
@@ -5,9 +5,9 @@
package com.artipie.front.rest;
import com.artipie.front.settings.ArtipieEndpoint;
-import java.net.http.HttpResponse;
+import io.vavr.Tuple3;
+import java.util.Optional;
import javax.json.Json;
-import javax.servlet.http.HttpServletResponse;
/**
* Auth-service.
@@ -29,22 +29,23 @@ public AuthService(final ArtipieEndpoint endpoint) {
}
/**
- * Obtains JWT-token from auth rest-service.
- *
+ * Obtain JWT-token from auth rest-service.
* @param name User name.
* @param password User password.
- * @return JWT-token.
+ * @return Tuple3 of 'Status code, JWT-token, Error message'.
*/
- public String getJwtToken(final String name, final String password) {
- final HttpResponse response = this.httpPost(
- AuthService.TOKEN_PATH,
- () ->
- Json.createObjectBuilder()
- .add("name", name)
- .add("pass", password)
- .build().toString()
+ public Tuple3 getJwtToken(final String name, final String password) {
+ return BaseService.handle(
+ this.httpPost(
+ Optional.empty(),
+ AuthService.TOKEN_PATH,
+ () ->
+ Json.createObjectBuilder()
+ .add("name", name)
+ .add("pass", password)
+ .build().toString()
+ ),
+ res -> BaseService.jsonObject(res).getString("token")
);
- checkStatus(HttpServletResponse.SC_OK, response);
- return BaseService.jsonObject(response).getString("token");
}
}
diff --git a/src/main/java/com/artipie/front/rest/BaseService.java b/src/main/java/com/artipie/front/rest/BaseService.java
index a7ac46e..0fa1d9f 100644
--- a/src/main/java/com/artipie/front/rest/BaseService.java
+++ b/src/main/java/com/artipie/front/rest/BaseService.java
@@ -5,7 +5,10 @@
package com.artipie.front.rest;
import com.artipie.ArtipieException;
+import com.artipie.front.misc.Json2Yaml;
import com.artipie.front.settings.ArtipieEndpoint;
+import com.google.common.net.HttpHeaders;
+import io.vavr.Tuple3;
import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
@@ -15,10 +18,13 @@
import java.net.http.HttpResponse;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
+import java.util.Optional;
+import java.util.function.Function;
import java.util.function.Supplier;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
+import javax.servlet.http.HttpServletResponse;
/**
* Base rest-service.
@@ -27,9 +33,9 @@
@SuppressWarnings({"PMD.DataClass", "PMD.AvoidFieldNameMatchingMethodName", "PMD.TooManyMethods"})
public class BaseService {
/**
- * Authorization header.
+ * Application json content-type value.
*/
- public static final String AUTHORIZATION = "Authorization";
+ public static final String APPLICATION_JSON = "application/json";
/**
* Http request timeout.
@@ -76,34 +82,18 @@ protected static String bearer(final String token) {
return String.format("Bearer %s", token);
}
- /**
- * Checks response status.
- * @param expected Expected status code.
- * @param response Response.
- * @throws ArtipieException In case mismatch expected status code in response.
- */
- protected static void checkStatus(final int expected, final HttpResponse response)
- throws ArtipieException {
- if (response.statusCode() != expected) {
- throw new ArtipieException(
- String.format(
- "Expected %s result code, but received %s", expected, response.statusCode()
- )
- );
- }
- }
-
/**
* Invokes GET http request.
+ * @param token JWT token.
* @param path Path in URL.
* @return Http response.
*/
- protected HttpResponse httpGet(final String path) {
+ protected HttpResponse httpGet(final Optional token, final String path) {
try {
return HttpClient.newBuilder()
.build()
.send(
- this.createGetRequest(path),
+ this.createGetRequest(token, path),
HttpResponse.BodyHandlers.ofString()
);
} catch (final IOException | InterruptedException exc) {
@@ -112,17 +102,19 @@ protected HttpResponse httpGet(final String path) {
}
/**
- * Invokes GET http request.
+ * Invokes POST http request.
* @param token JWT token.
* @param path Path in URL.
+ * @param payload Payload supplier.
* @return Http response.
*/
- protected HttpResponse httpGet(final String token, final String path) {
+ protected HttpResponse httpPost(final Optional token, final String path,
+ final Supplier payload) {
try {
return HttpClient.newBuilder()
.build()
.send(
- this.createGetRequest(token, path),
+ this.createPostRequest(token, path, payload),
HttpResponse.BodyHandlers.ofString()
);
} catch (final IOException | InterruptedException exc) {
@@ -132,16 +124,18 @@ protected HttpResponse httpGet(final String token, final String path) {
/**
* Invokes POST http request.
+ * @param token JWT token.
* @param path Path in URL.
* @param payload Payload supplier.
* @return Http response.
*/
- protected HttpResponse httpPost(final String path, final Supplier payload) {
+ protected HttpResponse httpPut(final Optional token, final String path,
+ final Supplier payload) {
try {
return HttpClient.newBuilder()
.build()
.send(
- this.createPostRequest(path, payload),
+ this.createPutRequest(token, path, payload),
HttpResponse.BodyHandlers.ofString()
);
} catch (final IOException | InterruptedException exc) {
@@ -150,19 +144,17 @@ protected HttpResponse httpPost(final String path, final Supplier httpPost(final String token, final String path,
- final Supplier payload) {
+ protected HttpResponse httpDelete(final Optional token, final String path) {
try {
return HttpClient.newBuilder()
.build()
.send(
- this.createPostRequest(token, path, payload),
+ this.createDeleteRequest(token, path),
HttpResponse.BodyHandlers.ofString()
);
} catch (final IOException | InterruptedException exc) {
@@ -176,70 +168,81 @@ protected HttpResponse httpPost(final String token, final String path,
* @param path Path in URL.
* @return Http request.
*/
- protected HttpRequest createGetRequest(final String token, final String path) {
+ protected HttpRequest createGetRequest(final Optional token, final String path) {
try {
- return HttpRequest.newBuilder()
+ final HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(this.uri(path))
.GET()
- .timeout(BaseService.TIMEOUT)
- .header(BaseService.AUTHORIZATION, bearer(token))
- .build();
+ .header(HttpHeaders.ACCEPT, BaseService.APPLICATION_JSON)
+ .timeout(BaseService.TIMEOUT);
+ token.ifPresent(value -> builder.header(HttpHeaders.AUTHORIZATION, bearer(value)));
+ return builder.build();
} catch (final URISyntaxException exc) {
throw new ArtipieException(exc);
}
}
/**
- * Creates GET http request.
+ * Creates POST http request.
+ * @param token JWT token.
* @param path Path in URL.
+ * @param payload Payload supplier.
* @return Http request.
*/
- protected HttpRequest createGetRequest(final String path) {
+ protected HttpRequest createPostRequest(final Optional token, final String path,
+ final Supplier payload) {
try {
- return HttpRequest.newBuilder()
+ final HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(this.uri(path))
- .GET()
- .timeout(BaseService.TIMEOUT)
- .build();
+ .POST(HttpRequest.BodyPublishers.ofString(payload.get()))
+ .header(HttpHeaders.ACCEPT, BaseService.APPLICATION_JSON)
+ .header(HttpHeaders.CONTENT_TYPE, BaseService.APPLICATION_JSON)
+ .timeout(BaseService.TIMEOUT);
+ token.ifPresent(value -> builder.header(HttpHeaders.AUTHORIZATION, bearer(value)));
+ return builder.build();
} catch (final URISyntaxException exc) {
throw new ArtipieException(exc);
}
}
/**
- * Creates POST http request.
+ * Creates PUT http request.
+ * @param token JWT token.
* @param path Path in URL.
* @param payload Payload supplier.
* @return Http request.
*/
- protected HttpRequest createPostRequest(final String path, final Supplier payload) {
+ protected HttpRequest createPutRequest(final Optional token, final String path,
+ final Supplier payload) {
try {
- return HttpRequest.newBuilder()
+ final HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(this.uri(path))
- .POST(HttpRequest.BodyPublishers.ofString(payload.get()))
- .timeout(BaseService.TIMEOUT)
- .build();
+ .PUT(HttpRequest.BodyPublishers.ofString(payload.get()))
+ .header(HttpHeaders.ACCEPT, BaseService.APPLICATION_JSON)
+ .header(HttpHeaders.CONTENT_TYPE, BaseService.APPLICATION_JSON)
+ .timeout(BaseService.TIMEOUT);
+ token.ifPresent(value -> builder.header(HttpHeaders.AUTHORIZATION, bearer(value)));
+ return builder.build();
} catch (final URISyntaxException exc) {
throw new ArtipieException(exc);
}
}
/**
- * Creates POST http request.
+ * Creates DELETE http request.
* @param token JWT token.
* @param path Path in URL.
- * @param payload Payload supplier.
* @return Http request.
*/
- protected HttpRequest createPostRequest(final String token, final String path,
- final Supplier payload) {
+ protected HttpRequest createDeleteRequest(final Optional token, final String path) {
try {
- return HttpRequest.newBuilder()
+ final HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(this.uri(path))
- .POST(HttpRequest.BodyPublishers.ofString(payload.get()))
- .header(BaseService.AUTHORIZATION, bearer(token))
- .timeout(BaseService.TIMEOUT)
- .build();
+ .DELETE()
+ .header(HttpHeaders.ACCEPT, BaseService.APPLICATION_JSON)
+ .timeout(BaseService.TIMEOUT);
+ token.ifPresent(value -> builder.header(HttpHeaders.AUTHORIZATION, bearer(value)));
+ return builder.build();
} catch (final URISyntaxException exc) {
throw new ArtipieException(exc);
}
@@ -262,4 +265,81 @@ protected static JsonObject jsonObject(final HttpResponse response) {
protected static JsonArray jsonArray(final HttpResponse response) {
return Json.createReader(new StringReader(response.body())).readArray();
}
+
+ /**
+ * Convert response json-body to yaml.
+ * @param response Response.
+ * @return Yaml content.
+ */
+ protected static String toYaml(final HttpResponse response) {
+ return new Json2Yaml().apply(BaseService.jsonObject(response).toString())
+ .toString();
+ }
+
+ /**
+ * Strip leading and ending quotes.
+ * @param str String with leading and ending quotes or without them.
+ * @return Stripped string.
+ */
+ protected static String stripQuotes(final String str) {
+ String result = str;
+ if (str.length() > 1 && str.charAt(0) == '"' && str.charAt(str.length() - 1) == '"') {
+ result = str.substring(1, str.length() - 1);
+ }
+ return result;
+ }
+
+ /**
+ * Check response result code and forms triple-tuple:
+ * 1. response status code.
+ * 2. resulting content in case success result code
+ * 3. error message based on response content in case receiveing of error in status code.
+ * @param response Response.
+ * @param map Map-function to form resulting content in case successful status code.
+ * @param Type of resulting of map-function.
+ * @return Triple-tuple.
+ */
+ protected static Tuple3 handle(final HttpResponse response,
+ final Function, V> map) {
+ return handle(HttpServletResponse.SC_OK, response, map);
+ }
+
+ /**
+ * Check response result code and forms triple-tuple:
+ * 1. response status code.
+ * 2. resulting content in case success result code
+ * 3. error message based on response content in case receiveing of error in status code.
+ * @param success Expected success result code.
+ * @param response Response.
+ * @param map Map-function to form resulting content in case successful status code.
+ * @param Type of resulting of map-function.
+ * @return Triple-tuple.
+ */
+ protected static Tuple3 handle(final int success,
+ final HttpResponse response, final Function, V> map) {
+ V content = null;
+ String error = null;
+ if (success == response.statusCode()) {
+ content = map.apply(response);
+ } else {
+ error = response.body();
+ }
+ return new Tuple3<>(response.statusCode(), content, error);
+ }
+
+ /**
+ * Join path-parts.
+ * @param parts Parts of path.
+ * @return Path as joined by '/'-symbol path-parts.
+ */
+ protected static String path(final Object...parts) {
+ final StringBuilder builder = new StringBuilder();
+ for (final Object path : parts) {
+ builder.append(path).append('/');
+ }
+ if (builder.length() > 0) {
+ builder.setLength(builder.length() - 1);
+ }
+ return builder.toString();
+ }
}
diff --git a/src/main/java/com/artipie/front/rest/RepositoryName.java b/src/main/java/com/artipie/front/rest/RepositoryName.java
new file mode 100644
index 0000000..8c54f73
--- /dev/null
+++ b/src/main/java/com/artipie/front/rest/RepositoryName.java
@@ -0,0 +1,141 @@
+/*
+ * The MIT License (MIT) Copyright (c) 2022 artipie.com
+ * https://github.com/artipie/front/LICENSE.txt
+ */
+package com.artipie.front.rest;
+
+import spark.Request;
+
+/**
+ * Repository name.
+ *
+ * @since 1.0
+ */
+public interface RepositoryName {
+ /**
+ * Repository name path parameter name.
+ */
+ String REPO = ":repo";
+
+ /**
+ * The name of the repository.
+ *
+ * @return String name
+ */
+ String toString();
+
+ /**
+ * Repository name from request.
+ *
+ * @since 1.0
+ */
+ class FromRequest implements RepositoryName {
+ /**
+ * Request.
+ */
+ private final Request request;
+
+ /**
+ * Layout.
+ */
+ private final String layout;
+
+ /**
+ * Ctor.
+ *
+ * @param request Request
+ * @param layout Layout
+ */
+ public FromRequest(final Request request, final String layout) {
+ this.request = request;
+ this.layout = layout;
+ }
+
+ /**
+ * Provides string representation of repository name in toString() method.
+ *
+ * 'reponame' for flat layout
+ * 'username/reponame' for org layout
+ *
+ *
+ * @checkstyle NoJavadocForOverriddenMethodsCheck (10 lines)
+ */
+ @Override
+ public String toString() {
+ final String reponame;
+ if ("flat".equals(this.layout)) {
+ reponame = new Flat(
+ this.request.params(RepositoryName.REPO)
+ ).toString();
+ } else {
+ reponame = new Org(
+ this.request.params(RepositoryName.REPO),
+ this.request.session().attribute("uname")
+ ).toString();
+ }
+ return reponame;
+ }
+ }
+
+ /**
+ * Repository name for flat layout.
+ *
+ * @since 1.0
+ */
+ class Flat implements RepositoryName {
+
+ /**
+ * Repository name.
+ */
+ private final String repo;
+
+ /**
+ * Ctor.
+ *
+ * @param repo Name
+ */
+ public Flat(final String repo) {
+ this.repo = repo;
+ }
+
+ @Override
+ public String toString() {
+ return this.repo;
+ }
+ }
+
+ /**
+ * Repository name for org layout is combined from username and reponame:
+ * 'username/reponame'.
+ *
+ * @since 1.0
+ */
+ class Org implements RepositoryName {
+
+ /**
+ * Repository name.
+ */
+ private final String repo;
+
+ /**
+ * User name.
+ */
+ private final String user;
+
+ /**
+ * Ctor.
+ *
+ * @param repo Repository name
+ * @param user User name
+ */
+ public Org(final String repo, final String user) {
+ this.repo = repo;
+ this.user = user;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s/%s", this.user, this.repo);
+ }
+ }
+}
diff --git a/src/main/java/com/artipie/front/rest/RepositoryService.java b/src/main/java/com/artipie/front/rest/RepositoryService.java
index f5fbcfa..1939160 100644
--- a/src/main/java/com/artipie/front/rest/RepositoryService.java
+++ b/src/main/java/com/artipie/front/rest/RepositoryService.java
@@ -4,13 +4,15 @@
*/
package com.artipie.front.rest;
+import com.artipie.front.misc.Yaml2Json;
import com.artipie.front.settings.ArtipieEndpoint;
+import io.vavr.Tuple3;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import javax.json.JsonArray;
import javax.json.JsonValue;
-import javax.servlet.http.HttpServletResponse;
/**
* Repository-service.
@@ -18,10 +20,17 @@
* @since 1.0
*/
public class RepositoryService extends BaseService {
+ /**
+ * Path to 'repository'.
+ */
+ private static final String REPOSITORY_PATH = "/api/v1/repository";
+
/**
* Path to 'list' rest-api.
*/
- private static final String LIST_PATH = "/api/v1/repository/list";
+ private static final String LIST_PATH = BaseService.path(
+ RepositoryService.REPOSITORY_PATH, "list"
+ );
/**
* Ctor.
@@ -33,27 +42,82 @@ public RepositoryService(final ArtipieEndpoint endpoint) {
}
/**
- * Obtains list of repository names.
+ * Obtain list of repository names.
* @param token Token.
- * @return List of repository names.
+ * @return Tuple3 of 'Status code, List of repository names, Error message'
*/
- public List list(final String token) {
- final HttpResponse response = this.httpGet(token, RepositoryService.LIST_PATH);
- checkStatus(HttpServletResponse.SC_OK, response);
- return RepositoryService.listOfStrings(response);
+ public Tuple3, String> list(final String token) {
+ return BaseService.handle(
+ this.httpGet(Optional.of(token), RepositoryService.LIST_PATH),
+ RepositoryService::listOfStrings
+ );
}
/**
- * Obtains list of repository names by user's name.
+ * Obtain list of repository names by user's name.
* @param token Token.
* @param uname User name.
- * @return List of repository names.
+ * @return Tuple3 of 'Status code, List of repository names, Error message'
+ */
+ public Tuple3, String> list(final String token, final String uname) {
+ return BaseService.handle(
+ this.httpGet(
+ Optional.of(token),
+ BaseService.path(RepositoryService.LIST_PATH, uname)
+ ),
+ RepositoryService::listOfStrings
+ );
+ }
+
+ /**
+ * Obtain repository content.
+ * @param token Token.
+ * @param rname Repository name.
+ * @return Tuple3 of 'Status code, Repository content, Error message'
+ */
+ public Tuple3 repo(final String token, final RepositoryName rname) {
+ return BaseService.handle(
+ this.httpGet(
+ Optional.of(token),
+ BaseService.path(RepositoryService.REPOSITORY_PATH, rname)
+ ),
+ BaseService::toYaml
+ );
+ }
+
+ /**
+ * Save repository config.
+ * @param token Token.
+ * @param rname Repository name.
+ * @param config Repository config.
+ * @return Tuple3 of 'Status code, Resulting message, Error message'
+ */
+ public Tuple3 save(final String token, final RepositoryName rname,
+ final String config) {
+ return BaseService.handle(
+ this.httpPut(
+ Optional.of(token),
+ BaseService.path(RepositoryService.REPOSITORY_PATH, rname),
+ () -> new Yaml2Json().apply(config).toString()
+ ),
+ res -> String.format("Repository %s saved successfully", rname)
+ );
+ }
+
+ /**
+ * Remove repository.
+ * @param token Token.
+ * @param rname Repository name.
+ * @return Tuple3 of 'Status code, Resulting message, Error message'
*/
- public List list(final String token, final String uname) {
- final HttpResponse response =
- this.httpGet(token, String.format("%s/%s", RepositoryService.LIST_PATH, uname));
- checkStatus(HttpServletResponse.SC_OK, response);
- return RepositoryService.listOfStrings(response);
+ public Tuple3 remove(final String token, final RepositoryName rname) {
+ return BaseService.handle(
+ this.httpDelete(
+ Optional.of(token),
+ RepositoryService.path(RepositoryService.REPOSITORY_PATH, rname.toString())
+ ),
+ res -> String.format("Repository %s removed", rname)
+ );
}
/**
@@ -66,7 +130,7 @@ private static List listOfStrings(final HttpResponse res) {
final JsonArray array = BaseService.jsonArray(res);
final List result = new ArrayList<>(array.size());
for (final JsonValue item : array) {
- result.add(item.toString());
+ result.add(BaseService.stripQuotes(item.toString()));
}
return result;
}
diff --git a/src/main/java/com/artipie/front/ui/PostSignIn.java b/src/main/java/com/artipie/front/ui/PostSignIn.java
index 409dd9c..f6e586a 100644
--- a/src/main/java/com/artipie/front/ui/PostSignIn.java
+++ b/src/main/java/com/artipie/front/ui/PostSignIn.java
@@ -4,8 +4,8 @@
*/
package com.artipie.front.ui;
-import com.artipie.ArtipieException;
import com.artipie.front.rest.AuthService;
+import io.vavr.Tuple3;
import java.util.Objects;
import org.eclipse.jetty.http.HttpStatus;
import spark.Request;
@@ -49,18 +49,22 @@ public Object handle(final Request req, final Response rsp) throws Exception {
Spark.halt(HttpStatus.BAD_REQUEST_400, "CRSF validation failed");
}
String result = null;
- try {
- final String token = this.auth.getJwtToken(
- req.queryParamOrDefault("username", ""),
- req.queryParamOrDefault("password", "")
- );
+ final Tuple3 res = this.auth.getJwtToken(
+ req.queryParamOrDefault("username", ""),
+ req.queryParamOrDefault("password", "")
+ );
+ if (res._3 == null) {
req.session().attribute("uid", "");
req.session().attribute("uname", req.queryParamOrDefault("username", ""));
- req.session().attribute("token", token);
- rsp.redirect("/dashboard/repository/list");
+ req.session().attribute("token", res._2);
+ rsp.redirect("/dashboard");
result = "OK";
- } catch (final ArtipieException exception) {
- Spark.halt(HttpStatus.UNAUTHORIZED_401, "bad credentials");
+ } else {
+ if (res._1 == HttpStatus.UNAUTHORIZED_401) {
+ Spark.halt(HttpStatus.UNAUTHORIZED_401, "bad credentials");
+ } else {
+ Spark.halt(res._1, res._3);
+ }
}
return result;
}
diff --git a/src/main/java/com/artipie/front/ui/RepoPage.java b/src/main/java/com/artipie/front/ui/RepoPage.java
deleted file mode 100644
index be27944..0000000
--- a/src/main/java/com/artipie/front/ui/RepoPage.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * The MIT License (MIT) Copyright (c) 2022 artipie.com
- * https://github.com/artipie/front/LICENSE.txt
- */
-package com.artipie.front.ui;
-
-import com.amihaiemil.eoyaml.Yaml;
-import com.amihaiemil.eoyaml.YamlMapping;
-import com.artipie.front.RequestAttr;
-import com.artipie.front.api.Repositories;
-import com.artipie.front.api.Users;
-import com.artipie.front.misc.RouteWrap;
-import com.artipie.front.misc.ValueFromBody;
-import com.artipie.front.settings.RepoSettings;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
-import org.eclipse.jetty.http.HttpStatus;
-import spark.Request;
-import spark.Response;
-import spark.Route;
-
-/**
- * Dashboard repo page.
- * @since 0.1
- */
-@SuppressWarnings("PMD.AvoidDuplicateLiterals")
-public final class RepoPage {
-
- /**
- * Dashboard repo page template view.
- * @since 0.1
- */
- public static final class TemplateView extends RouteWrap.TemplateViewRoute {
-
- /**
- * Template view wrap route.
- *
- * @param repos Repository settings
- */
- public TemplateView(final RepoSettings repos) {
- super(
- new HbPage(
- "repo",
- req -> {
- final String uid = RequestAttr.Standard.USER_ID.readOrThrow(req);
- final String name = Repositories.REPO_PARAM.parse(req);
- final Map res = new HashMap<>(5);
- res.put("user", uid);
- res.put("title", uid);
- res.put("name", name);
- if (repos.exists(name, uid)) {
- try {
- final YamlMapping yaml = Yaml.createYamlInput(
- new ByteArrayInputStream(repos.value(name, uid))
- ).readYamlMapping();
- res.put("type", yaml.yamlMapping("repo").string("type"));
- res.put("config", yaml.toString());
- } catch (final IOException err) {
- throw new UncheckedIOException(err);
- }
- } else {
- res.put("type", req.queryParamOrDefault("type", "maven"));
- }
- return res;
- }
- )
- );
- }
- }
-
- /**
- * Handles post request from dashboard repo page to remove/update repository.
- * @since 0.1
- * @checkstyle ExecutableStatementCountCheck (100 lines)
- * @checkstyle ReturnCountCheck (100 lines)
- */
- public static final class Post implements Route {
-
- /**
- * Repository settings.
- */
- private final RepoSettings stn;
-
- /**
- * Ctor.
- * @param stn Repository settings
- */
- public Post(final RepoSettings stn) {
- this.stn = stn;
- }
-
- @Override
- @SuppressWarnings("PMD.OnlyOneReturn")
- public Object handle(final Request request, final Response response) {
- final ValueFromBody vals = new ValueFromBody(request.body());
- final String action = vals.byNameOrThrow("action");
- final String name = vals.byNameOrThrow("repo");
- final String uid = Users.USER_PARAM.parse(request);
- if ("update".equals(action)) {
- final YamlMapping yaml = Post.configsFromBody(vals);
- final YamlMapping repo = yaml.yamlMapping("repo");
- if (repo == null) {
- response.status(HttpStatus.BAD_REQUEST_400);
- return "Repo section is required";
- }
- if (repo.value("type") == null) {
- response.status(HttpStatus.BAD_REQUEST_400);
- return "Repository type is required";
- }
- if (repo.value("storage") == null) {
- response.status(HttpStatus.BAD_REQUEST_400);
- return "Repository storage is required";
- }
- if (this.stn.exists(name, uid)) {
- this.stn.delete(name, uid);
- }
- this.stn.save(name, uid, yaml.toString().getBytes(StandardCharsets.UTF_8));
- response.redirect(String.format("/dashboard/%s/%s", uid, name));
- } else if ("delete".equals(action)) {
- this.stn.delete(name, uid);
- response.redirect("/dashboard");
- }
- response.status(HttpStatus.FOUND_302);
- return null;
- }
-
- /**
- * Obtains config from body.
- * @param vals Values in body
- * @return Config content from body
- */
- private static YamlMapping configsFromBody(final ValueFromBody vals) {
- try {
- return Yaml.createYamlInput(vals.byNameOrThrow("config")).readYamlMapping();
- } catch (final IOException err) {
- throw new UncheckedIOException(err);
- }
- }
- }
-
- /**
- * Handle get request while creating new repository.
- * @since 0.1
- */
- public static final class Get implements Route {
-
- @Override
- public Object handle(final Request request, final Response response) {
- response.status(HttpStatus.FOUND_302);
- response.redirect(
- String.format(
- "/dashboard/%s/%s?type=%s",
- RequestAttr.Standard.USER_ID.readOrThrow(request),
- request.queryParams("repo"),
- request.queryParams("type")
- )
- );
- return null;
- }
- }
-
-}
diff --git a/src/main/java/com/artipie/front/ui/UserPage.java b/src/main/java/com/artipie/front/ui/UserPage.java
deleted file mode 100644
index 2efe251..0000000
--- a/src/main/java/com/artipie/front/ui/UserPage.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * The MIT License (MIT) Copyright (c) 2022 artipie.com
- * https://github.com/artipie/front/LICENSE.txt
- */
-package com.artipie.front.ui;
-
-import com.artipie.front.RequestAttr;
-import com.artipie.front.misc.RouteWrap;
-import com.artipie.front.settings.RepoSettings;
-import java.util.Map;
-import java.util.Optional;
-
-/**
- * Dashboard user page.
- * @since 0.1
- */
-@SuppressWarnings("PMD.AvoidDuplicateLiterals")
-public final class UserPage extends RouteWrap.TemplateViewRoute {
-
- /**
- * New user page.
- * @param repos Artipie repository settings
- */
- public UserPage(final RepoSettings repos) {
- super(
- new HbPage(
- "user",
- req -> {
- final String uid = RequestAttr.Standard.USER_ID.readOrThrow(req);
- return Map.of(
- "user", uid, "title", uid,
- "repos", repos.list(Optional.of(uid))
- );
- }
- )
- );
- }
-}
diff --git a/src/main/java/com/artipie/front/ui/repository/RepoAddConfig.java b/src/main/java/com/artipie/front/ui/repository/RepoAddConfig.java
new file mode 100644
index 0000000..bbb1172
--- /dev/null
+++ b/src/main/java/com/artipie/front/ui/repository/RepoAddConfig.java
@@ -0,0 +1,66 @@
+/*
+ * The MIT License (MIT) Copyright (c) 2022 artipie.com
+ * https://github.com/artipie/front/LICENSE.txt
+ */
+package com.artipie.front.ui.repository;
+
+import com.artipie.front.misc.RouteWrap;
+import com.artipie.front.rest.RepositoryName;
+import com.artipie.front.ui.HbPage;
+import java.util.Map;
+
+/**
+ * Add repository config view page.
+ *
+ * @since 1.0
+ */
+@SuppressWarnings("PMD.AvoidDuplicateLiterals")
+public final class RepoAddConfig extends RouteWrap.TemplateViewRoute {
+ /**
+ * Add repository info page.
+ * @param layout Layout
+ * @param info Infor template
+ * @param template Repository template
+ */
+ public RepoAddConfig(final String layout, final RepositoryInfo info,
+ final RepositoryTemplate template) {
+ super(
+ new HbPage(
+ "repository/add_config",
+ req -> {
+ final String name = req.queryParams("name");
+ final String type = req.queryParams("type");
+ final String user = req.session().attribute("uname");
+ final String rname;
+ if ("flat".equals(layout)) {
+ rname = new RepositoryName.Flat(name).toString();
+ } else {
+ rname = new RepositoryName.Org(name, user).toString();
+ }
+ return Map.of(
+ "title", "Add repository",
+ "uname", user,
+ "rname", rname,
+ "type", type,
+ "info", info.render(
+ type,
+ Map.of(
+ "user", user,
+ "repo", name,
+ "type", type
+ )
+ ),
+ "template", template.render(
+ type,
+ Map.of(
+ "user", user,
+ "repo", name,
+ "type", type
+ )
+ )
+ );
+ }
+ )
+ );
+ }
+}
diff --git a/src/main/java/com/artipie/front/ui/repository/RepoAddInfo.java b/src/main/java/com/artipie/front/ui/repository/RepoAddInfo.java
new file mode 100644
index 0000000..f598741
--- /dev/null
+++ b/src/main/java/com/artipie/front/ui/repository/RepoAddInfo.java
@@ -0,0 +1,32 @@
+/*
+ * The MIT License (MIT) Copyright (c) 2022 artipie.com
+ * https://github.com/artipie/front/LICENSE.txt
+ */
+package com.artipie.front.ui.repository;
+
+import com.artipie.front.misc.RouteWrap;
+import com.artipie.front.ui.HbPage;
+import java.util.Map;
+
+/**
+ * Add repository info view page.
+ *
+ * @since 1.0
+ */
+@SuppressWarnings("PMD.AvoidDuplicateLiterals")
+public final class RepoAddInfo extends RouteWrap.TemplateViewRoute {
+ /**
+ * Add repository info page.
+ */
+ public RepoAddInfo() {
+ super(
+ new HbPage(
+ "repository/add_info",
+ req -> Map.of(
+ "title", "Add repository",
+ "uname", req.session().attribute("uname")
+ )
+ )
+ );
+ }
+}
diff --git a/src/main/java/com/artipie/front/ui/repository/RepoEdit.java b/src/main/java/com/artipie/front/ui/repository/RepoEdit.java
new file mode 100644
index 0000000..2751699
--- /dev/null
+++ b/src/main/java/com/artipie/front/ui/repository/RepoEdit.java
@@ -0,0 +1,96 @@
+/*
+ * The MIT License (MIT) Copyright (c) 2022 artipie.com
+ * https://github.com/artipie/front/LICENSE.txt
+ */
+package com.artipie.front.ui.repository;
+
+import com.amihaiemil.eoyaml.Yaml;
+import com.amihaiemil.eoyaml.YamlMapping;
+import com.artipie.front.misc.RouteWrap;
+import com.artipie.front.rest.RepositoryName;
+import com.artipie.front.rest.RepositoryService;
+import com.artipie.front.ui.HbPage;
+import io.vavr.Tuple3;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Optional;
+import org.eclipse.jetty.io.RuntimeIOException;
+
+/**
+ * Repository editor page.
+ *
+ * @since 1.0
+ */
+@SuppressWarnings("PMD.AvoidDuplicateLiterals")
+public final class RepoEdit extends RouteWrap.TemplateViewRoute {
+ /**
+ * Repository editor page.
+ *
+ * @param repository Repository service.
+ * @param layout Layout.
+ * @param info Repository info templates.
+ */
+ public RepoEdit(final RepositoryService repository, final String layout,
+ final RepositoryInfo info) {
+ super(
+ new HbPage(
+ "repository/edit",
+ req -> {
+ final RepositoryName rname = new RepositoryName.FromRequest(req, layout);
+ final String repo = req.params(":repo");
+ final String user = req.session().attribute("uname");
+ final Tuple3 result = repository.repo(
+ req.session().attribute("token"), rname
+ );
+ return Map.of(
+ "title", String.format("Repository %s", rname),
+ "rname", rname,
+ "result", result,
+ "error", "",
+ "info", RepoEdit.repoType(result._2)
+ .map(
+ type -> {
+ String rendered;
+ try {
+ rendered = info.render(
+ type,
+ Map.of(
+ "user", user,
+ "repo", repo,
+ "type", type
+ )
+ );
+ } catch (final RuntimeIOException exc) {
+ rendered = "";
+ }
+ return rendered;
+ }
+ ).orElse("")
+ );
+ }
+ )
+ );
+ }
+
+ /**
+ * Provides repository type from yaml-configuration.
+ * @param repoconf Yaml-content of repository configuration.
+ * @return Repository type.
+ */
+ private static Optional repoType(final String repoconf) {
+ return Optional.ofNullable(repoconf)
+ .flatMap(
+ content -> {
+ Optional yaml;
+ try {
+ yaml = Optional.of(Yaml.createYamlInput(content).readYamlMapping());
+ } catch (final IOException exc) {
+ yaml = Optional.empty();
+ }
+ return yaml;
+ }
+ )
+ .map(yaml -> yaml.yamlMapping("repo"))
+ .map(repo -> repo.string("type"));
+ }
+}
diff --git a/src/main/java/com/artipie/front/ui/repository/RepoList.java b/src/main/java/com/artipie/front/ui/repository/RepoList.java
index 65d34bf..dd22dc2 100644
--- a/src/main/java/com/artipie/front/ui/repository/RepoList.java
+++ b/src/main/java/com/artipie/front/ui/repository/RepoList.java
@@ -7,6 +7,8 @@
import com.artipie.front.misc.RouteWrap;
import com.artipie.front.rest.RepositoryService;
import com.artipie.front.ui.HbPage;
+import io.vavr.Tuple3;
+import java.util.List;
import java.util.Map;
/**
@@ -21,16 +23,27 @@ public final class RepoList extends RouteWrap.TemplateViewRoute {
* List of repositories page.
*
* @param repository Repository service.
+ * @param layout Layout.
*/
- public RepoList(final RepositoryService repository) {
+ public RepoList(final RepositoryService repository, final String layout) {
super(
new HbPage(
"repository/list",
- req ->
- Map.of(
+ req -> {
+ final String uname = req.session().attribute("uname");
+ final String token = req.session().attribute("token");
+ final Tuple3, String> result;
+ if ("flat".equals(layout)) {
+ result = repository.list(token);
+ } else {
+ result = repository.list(token, uname);
+ }
+ return Map.of(
"title", "Repository list",
- "repos", repository.list(req.session().attribute("token"))
- )
+ "result", result,
+ "error", ""
+ );
+ }
)
);
}
diff --git a/src/main/java/com/artipie/front/ui/repository/RepoRemove.java b/src/main/java/com/artipie/front/ui/repository/RepoRemove.java
new file mode 100644
index 0000000..a05099f
--- /dev/null
+++ b/src/main/java/com/artipie/front/ui/repository/RepoRemove.java
@@ -0,0 +1,46 @@
+/*
+ * The MIT License (MIT) Copyright (c) 2022 artipie.com
+ * https://github.com/artipie/front/LICENSE.txt
+ */
+package com.artipie.front.ui.repository;
+
+import com.artipie.front.misc.RouteWrap;
+import com.artipie.front.rest.RepositoryName;
+import com.artipie.front.rest.RepositoryService;
+import com.artipie.front.ui.HbPage;
+import java.util.Map;
+
+/**
+ * Repository remove POST-handler.
+ * Removes repository and shows result of on page.
+ *
+ * @since 1.0
+ */
+@SuppressWarnings("PMD.AvoidDuplicateLiterals")
+public final class RepoRemove extends RouteWrap.TemplateViewRoute {
+ /**
+ * Repository delete.
+ *
+ * @param repository Repository service.
+ * @param layout Layout.
+ */
+ public RepoRemove(final RepositoryService repository, final String layout) {
+ super(
+ new HbPage(
+ "repository/result",
+ req -> {
+ final RepositoryName rname = new RepositoryName.FromRequest(req, layout);
+ return Map.of(
+ "title", String.format("Repository %s", rname),
+ "rname", rname,
+ "result", repository.remove(req.session().attribute("token"), rname),
+ "error", "",
+ "redirectUrl", "/dashboard/repository/list",
+ "redirectMessage", "Continue"
+ );
+ }
+ )
+ );
+ }
+}
+
diff --git a/src/main/java/com/artipie/front/ui/repository/RepoSave.java b/src/main/java/com/artipie/front/ui/repository/RepoSave.java
new file mode 100644
index 0000000..15400af
--- /dev/null
+++ b/src/main/java/com/artipie/front/ui/repository/RepoSave.java
@@ -0,0 +1,50 @@
+/*
+ * The MIT License (MIT) Copyright (c) 2022 artipie.com
+ * https://github.com/artipie/front/LICENSE.txt
+ */
+package com.artipie.front.ui.repository;
+
+import com.artipie.front.misc.RouteWrap;
+import com.artipie.front.rest.RepositoryName;
+import com.artipie.front.rest.RepositoryService;
+import com.artipie.front.ui.HbPage;
+import java.util.Map;
+
+/**
+ * Repository configuration saver.
+ * Saves repository configuration and shows result of saving on page.
+ *
+ * @since 1.0
+ */
+@SuppressWarnings("PMD.AvoidDuplicateLiterals")
+public final class RepoSave extends RouteWrap.TemplateViewRoute {
+ /**
+ * List of repositories page.
+ *
+ * @param repository Repository service.
+ * @param layout Layout.
+ */
+ public RepoSave(final RepositoryService repository, final String layout) {
+ super(
+ new HbPage(
+ "repository/result",
+ req -> {
+ final RepositoryName rname = new RepositoryName.FromRequest(req, layout);
+ return Map.of(
+ "title", String.format("Repository %s", rname),
+ "rname", rname,
+ "result", repository.save(
+ req.session().attribute("token"),
+ rname,
+ req.queryParams("config")
+ ),
+ "error", "",
+ "redirectUrl", String.format("/dashboard/repository/edit/%s", rname),
+ "redirectMessage", "Continue"
+ );
+ }
+ )
+ );
+ }
+}
+
diff --git a/src/main/java/com/artipie/front/ui/repository/RepositoryInfo.java b/src/main/java/com/artipie/front/ui/repository/RepositoryInfo.java
new file mode 100644
index 0000000..62a6252
--- /dev/null
+++ b/src/main/java/com/artipie/front/ui/repository/RepositoryInfo.java
@@ -0,0 +1,68 @@
+/*
+ * The MIT License (MIT) Copyright (c) 2022 artipie.com
+ * https://github.com/artipie/front/LICENSE.txt
+ */
+package com.artipie.front.ui.repository;
+
+import com.artipie.front.ui.HbTemplateEngine;
+import java.util.Map;
+import spark.ModelAndView;
+
+/**
+ * Info-template renderer.
+ * Provides 'html'-template with information about repository usage of specified type.
+ * All info-templates are situated in '/info'-resource folder.
+ *
+ * @since 1.0
+ */
+public final class RepositoryInfo {
+ /**
+ * Format for info-template.
+ */
+ private static final String FORMAT = "%s.info.html";
+
+ /**
+ * Type-aliases.
+ */
+ private static final Map ALIAS = Map.of("binary", "file");
+
+ /**
+ * Template engine.
+ */
+ private final HbTemplateEngine template;
+
+ /**
+ * Ctor.
+ */
+ public RepositoryInfo() {
+ this.template = new HbTemplateEngine("/info");
+ }
+
+ /**
+ * Renders template.
+ * @param type Repository type.
+ * @param model Model.
+ * @return Rendered template
+ */
+ public String render(final String type, final Map model) {
+ return this.template.render(
+ new ModelAndView(
+ model,
+ String.format(RepositoryInfo.FORMAT, RepositoryInfo.resolveAlias(type))
+ )
+ );
+ }
+
+ /**
+ * Resolve alias-type to type.
+ * @param alias Alias-type.
+ * @return Resolved type.
+ */
+ private static String resolveAlias(final String alias) {
+ String type = alias;
+ if (RepositoryInfo.ALIAS.containsKey(alias)) {
+ type = RepositoryInfo.ALIAS.get(alias);
+ }
+ return type;
+ }
+}
diff --git a/src/main/java/com/artipie/front/ui/repository/RepositoryTemplate.java b/src/main/java/com/artipie/front/ui/repository/RepositoryTemplate.java
new file mode 100644
index 0000000..f613ffd
--- /dev/null
+++ b/src/main/java/com/artipie/front/ui/repository/RepositoryTemplate.java
@@ -0,0 +1,74 @@
+/*
+ * The MIT License (MIT) Copyright (c) 2022 artipie.com
+ * https://github.com/artipie/front/LICENSE.txt
+ */
+package com.artipie.front.ui.repository;
+
+import com.artipie.front.ui.HbTemplateEngine;
+import java.util.List;
+import java.util.Map;
+import spark.ModelAndView;
+
+/**
+ * Repository template renderer.
+ * Provides 'yaml'-template of repository configuration.
+ * Provides 'yaml'-template with repository configuration of specified type.
+ * All templates are situated in '/template'-resource folder.
+ *
+ * @since 1.0
+ */
+public final class RepositoryTemplate {
+ /**
+ * Default template.
+ */
+ private static final String DEFAULT_TEMPLATE = "default";
+
+ /**
+ * Format for template.
+ */
+ private static final String FORMAT = "%s.template.yaml";
+
+ /**
+ * Pre-defined template names.
+ */
+ private static final List PREDEFINED = List.of("maven-group", "maven-proxy");
+
+ /**
+ * Template engine.
+ */
+ private final HbTemplateEngine template;
+
+ /**
+ * Ctor.
+ */
+ public RepositoryTemplate() {
+ this.template = new HbTemplateEngine("/template");
+ }
+
+ /**
+ * Render template.
+ *
+ * @param type Repository type.
+ * @param model Model.
+ * @return Rendered yaml-template for repository type.
+ */
+ public String render(final String type, final Map model) {
+ final String content;
+ if (RepositoryTemplate.PREDEFINED.contains(type)) {
+ content = this.template.render(
+ new ModelAndView(
+ model,
+ String.format(RepositoryTemplate.FORMAT, type)
+ )
+ );
+ } else {
+ content = this.template.render(
+ new ModelAndView(
+ model,
+ String.format(RepositoryTemplate.FORMAT, RepositoryTemplate.DEFAULT_TEMPLATE)
+ )
+ );
+ }
+ return content;
+ }
+}
diff --git a/src/main/java/com/artipie/front/ui/repository/flat/package-info.java b/src/main/java/com/artipie/front/ui/repository/flat/package-info.java
deleted file mode 100644
index 8f6d1dc..0000000
--- a/src/main/java/com/artipie/front/ui/repository/flat/package-info.java
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- * The MIT License (MIT) Copyright (c) 2022 artipie.com
- * https://github.com/artipie/front/LICENSE.txt
- */
-
-/**
- * Artipie front web service.
- * @since 1.0
- */
-package com.artipie.front.ui.repository.flat;
diff --git a/src/main/java/com/artipie/front/ui/repository/org/RepoListByUser.java b/src/main/java/com/artipie/front/ui/repository/org/RepoListByUser.java
deleted file mode 100644
index 98eb831..0000000
--- a/src/main/java/com/artipie/front/ui/repository/org/RepoListByUser.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * The MIT License (MIT) Copyright (c) 2022 artipie.com
- * https://github.com/artipie/front/LICENSE.txt
- */
-package com.artipie.front.ui.repository.org;
-
-import com.artipie.front.misc.RouteWrap;
-import com.artipie.front.rest.RepositoryService;
-import com.artipie.front.ui.HbPage;
-import java.util.Map;
-
-/**
- * List of user repositories page.
- *
- * @checkstyle AvoidDuplicateLiterals (500 lines)
- * @since 1.0
- */
-@SuppressWarnings("PMD.AvoidDuplicateLiterals")
-public final class RepoListByUser extends RouteWrap.TemplateViewRoute {
- /**
- * List of user repositories page.
- *
- * @param repository Repository service.
- */
- public RepoListByUser(final RepositoryService repository) {
- super(
- new HbPage(
- "repository/org/list",
- req -> {
- String error = "";
- final String uname = req.session().attribute("uname");
- if (!uname.equals(req.params(":uname"))) {
- error = "Access denied";
- }
- return Map.of(
- "title", String.format("Repository list by %s", uname),
- "uname", uname,
- "repos", repository.list(req.session().attribute("token"), uname),
- "error", error
- );
- }
- )
- );
- }
-}
diff --git a/src/main/java/com/artipie/front/ui/repository/org/package-info.java b/src/main/java/com/artipie/front/ui/repository/org/package-info.java
deleted file mode 100644
index d3bf97f..0000000
--- a/src/main/java/com/artipie/front/ui/repository/org/package-info.java
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- * The MIT License (MIT) Copyright (c) 2022 artipie.com
- * https://github.com/artipie/front/LICENSE.txt
- */
-
-/**
- * Artipie front web service.
- * @since 1.0
- */
-package com.artipie.front.ui.repository.org;
diff --git a/src/main/resources/html/base b/src/main/resources/html/base
index 81442d9..1c5e165 100644
--- a/src/main/resources/html/base
+++ b/src/main/resources/html/base
@@ -18,12 +18,33 @@
-
-
- {{#block "content"}}
- {{/block}}
-
-
+
+
+
+
+
+
+
+
+ {{#block "content"}}
+ {{/block}}
+
+
+
+
+
diff --git a/src/main/resources/html/repo b/src/main/resources/html/repo
deleted file mode 100644
index 92341ee..0000000
--- a/src/main/resources/html/repo
+++ /dev/null
@@ -1,295 +0,0 @@
-{{#partial "content"}}
-
-
- This is YAML config
- of your repository {{user}}/{{name}}
- (read this
- README
- file to get help):
-
-
-
-{{#if config}}
-
-{{else}}
-
-
-
-
-
-{{/if}}
-
-{{#eq type "maven"}}
-With this confirmation,
-the GitHub user @{{user}}
-will be able to publish Maven artifacts and all other users will be able to download.
-
-This is how you may configure it inside your
-pom.xml :
-
-<project>
- [...]
- <distributionManagement>
- <snapshotRepository>
- <id>artipie</id>
- <url>https://central.artipie.com/{{user}}/{{name}}</url>
- </snapshotRepository>
- <repository>
- <id>artipie</id>
- <url>https://central.artipie.com/{{user}}/{{name}}</url>
- </repository>
- </distributionManagement>
- <repositories>
- <repository>
- <id>artipie</id>
- <url>https://central.artipie.com/{{user}}/{{name}}</url>
- </repository>
- </repositories>
-</project>
-
-You publish just with
-mvn deploy
-and you download with
-mvn compile .
-{{/eq}}{{#eq type "maven-proxy"}}
-
Maven proxy repository proxies all request to remote Maven repository appling authentication
-and saves artifacts in Artipie storage cache.
-To use it as a mirror for Apache central, add this configuration to your
-~/.m2/settings.xml:
-
-<settings>
- [...]
- <mirrors>
- <mirror>
- <id>artipie-mirror</id>
- <name>Artipie Mirror Repository</name>
- <url>https://central.artipie.com/{{user}}/{{repo}}</url>
- <mirrorOf>central</mirrorOf>
- </mirror>
- </mirrors>
-</settings>
-
-
-{{/eq}}{{#eq type "docker"}}
-Tag your image with central.artipie.com/{{user}}/{{repo}} image prefix,
-and push it to central.artipie.com then. E.g.
-for alpine:3.11 use:
-
-docker tag alpine:3.11 central.artipie.com/{{user}}/{{repo}}alpine:3.11
-docker push central.artipie.com/{{user}}/{{repo}}alpine:3.11
-
-
-{{/eq}}
-{{#eq type "maven-group"}}
-To use Maven grouped (virtual) repository first create at least two Maven or Maven proxy repositories.
-It can be either just a Maven repository or Maven proxy (mirror) repository. Then specify
-https://central.artipie.com/{{user}}/{{repo}} as distribution management, or
-repositories in pom.xml or as a mirror in settings.xml (see
-more details in concrete repositories documentation). Then group repositories under
-group-settings, pay attention that order does matter: first repository in the list will be
-accessed first, then second, etc. Repository name should be fully formatted, include your username
-prefix, e.g. {{user}}/maven.
-{{/eq}}{{#eq type "file"}}
-It's just a binary files repository. Use PUT HTTP request to upload a file,
-and GET for downloading.
-E.g. using HTTPie CLI tool:
-
-# uploading file.bin
-http -a {{user}}:password PUT https://central.artipie.com/{{user}}/{{repo}}file.bin @file.bin
-# downloading file.bin
-http GET https://central.artipie.com/{{user}}/{{repo}}file.bin --output=./file.bin
-
-{{/eq}}{{#eq type "pypi"}}
-To publish your packages with twine
-specify Artipie repository url with --repository-url option
-
-$ twine upload --repository-url http://central.artipie.com/{{user}}/{{name}}/ -u {{user}} -p password myproject/dist/*
-
-To install package with pip install specify Artipie repository url with --index-url option:
-
-$ pip install --index-url http://{{user}}:password@central.artipie.com/{{user}}/{{name}}/ myproject
-
-{{/eq}}{{#eq type "deb"}}
-Add local repository to the list of Debian packages for apt by adding
-the following line to the /etc/apt/sources.list:
-
-deb [trusted=yes] http://{{user}}:password@central.artipie.com/{{user}}/{{name}} {{name}} main
-
-Then use apt-get as usual.
-{{/eq}}{{#eq type "go"}}
-To use go repository declare the following environment variables:
-
-export GO111MODULE=on
-export GOPROXY="central.artipie.com/{{user}}/{{name}}"
-
-Now you can install packages directly from Artipie:
-
-go get -x -insecure golang.org/x/time
-
-{{/eq}}{{#eq type "rpm"}}
-To install packages from rpm Artipie repository, add the following file
-to yum settings by path /etc/yum.repos.d/example.repo:
-
-[example]
-name=Example Repository
-baseurl=http://{{user}}:password@central.artipie.com/{{user}}/{{name}}
-enabled=1
-gpgcheck=0
-
-Then list and install packages with specified repository:
-
-yum -y repo-pkgs example list
-yum -y repo-pkgs example install
-
-{{/eq}}{{#eq type "conda"}}
-To install conda packages from Artipie, add repository to conda channels settings to /root/.condarc file (check
-documentation for more details):
-
-channels:
- - http://central.artipie.com/{{user}}/{{name}}
-
-Set Artipie repository url for upload to anaconda config:
-
-anaconda config --set url "http://central.artipie.com/{{user}}/{{name}}" -s
-
-You can also set automatic upload after building package:
-
-conda config --set anaconda_upload yes
-
-
-Now you can install packages from Artipie anaconda repository using conda install
-command and build and upload packages with conda build, or, if the package is already build,
-use anaconda upload command to publish package to Artipie.
-{{/eq}}{{#eq type "npm"}}
-To install or publish npm package into Artipie repository, specify the repository url
-with the --registry option:
-
- npm publish --registry=https://central.artipie.com/{{user}}/{{name}}
- npm install --registry=https://central.artipie.com/{{user}}/{{name}}
-
-{{/eq}}{{#eq type "gem"}}
-Before uploading your gems, obtain a key for authorization. A base64 encoded
-login:password would be a valid key:
-
- export GEM_HOST_API_KEY=$(echo -n "{{user}}:****" | base64)
-
-Now, to upload .gem file into Artipie repository, use gem push command:
-
- gem push --host https://central.artipie.com/{{user}}/{{name}}
-
-To install an existing gem, use gem install command:
-
- gem install awesome_gem --source https://central.artipie.com/{{user}}/{{name}}
-
-{{/eq}}{{#eq type "nuget"}}
-To install or publish NuGet package into Artipie, specify repository url
-and credentials in NuGet.Config xml file:
-
- <?xml version="1.0" encoding="utf-8"?>
- <configuration>
- <packageSources>
- <add key="artipie-repo" value="https://central.artipie.com/{{user}}/{{name}}/index.json" />
- </packageSources>
- <packageSourceCredentials>
- <artipie-nuget-test>
- <add key="Username" value="{{user}}"/>
- <add key="ClearTextPassword" value="****"/>
- </artipie-nuget-test>
- </packageSourceCredentials>
- </configuration>
-
-Then use this configuration when installing or publishing NuGet packages:
-
-nuget push ./nuget-package -ConfigFile ./NuGet.Config -Verbosity detailed -Source artipie-repo
-nuget install Newtonsoft.Json -Version 12.0.3 -NoCache -ConfigFile ./NuGet.Config -Source artipie-repo
-
-{{/eq}}{{#eq type "helm"}}
-To install chart from Artipie, add repository url to helm and run helm install:
-
-helm repo add artipie https://central.artipie.com/{{user}}/{{name}}
-helm install my_chart artipie
-
-To upload chart, perform POST request:
-
- curl -i -X POST --data-binary "@my-chart.tgz" https://central.artipie.com/{{user}}/{{name}}
-
-{{/eq}}{{#eq type "php"}}
-To publish your PHP Composer package create package description JSON file my-package.json with the following content:
-
-{
- "name": "my-org/my-package",
- "version": "1.0.0",
- "dist": {
- "url": "https://www.my-org.com/files/my-package.1.0.0.zip",
- "type": "zip"
- }
-}
-
-And add it to repository using PUT request:
-
-$ curl -X PUT -T 'my-package.json' "http://localhost:8080/my-php"
-
-To use this library in your project add requirement and repository to composer.json:
-
-{
- "repositories": [
- {"type": "composer", "url": "http://localhost:8080/my-php"}
- ],
- "require": {
- "my-org/my-package": "1.0.0"
- }
-}
-
-{{/eq}}
-
-{{/partial}}
-{{> base}}
diff --git a/src/main/resources/html/repository/add b/src/main/resources/html/repository/add
new file mode 100644
index 0000000..aba3d00
--- /dev/null
+++ b/src/main/resources/html/repository/add
@@ -0,0 +1,27 @@
+{{#partial "content"}}
+
+
+ This is YAML config of your repository {{rname}}
+ (read this README file to get help):
+
+{{#unless error}}
+ {{#if result._2}}
+
+
+
+{{result._2}}
+
+
+
+
+
+ {{else}}
+ Error: {{result._3}}
+ Status cde: {{result._1}}
+ {{/if}}
+{{else}}
+ Error: {{error}}
+{{/unless}}
+
+{{/partial}}
+{{> base}}
\ No newline at end of file
diff --git a/src/main/resources/html/repository/add_config b/src/main/resources/html/repository/add_config
new file mode 100644
index 0000000..5afcd0e
--- /dev/null
+++ b/src/main/resources/html/repository/add_config
@@ -0,0 +1,23 @@
+{{#partial "content"}}
+
+
+ This is YAML config
+ of your repository {{rname}}
+ (read this
+ README
+ file to get help):
+
+
+
+
+
+{{template}}
+
+
+
+
+
+{{{info}}}
+
+{{/partial}}
+{{> base}}
diff --git a/src/main/resources/html/user b/src/main/resources/html/repository/add_info
similarity index 71%
rename from src/main/resources/html/user
rename to src/main/resources/html/repository/add_info
index 0704497..e465716 100644
--- a/src/main/resources/html/user
+++ b/src/main/resources/html/repository/add_info
@@ -1,26 +1,8 @@
{{#partial "content"}}
-Obtain
-GitHub personal access token
-and use it as a password in authentication through package managers,
-your username is github.com/{{user}}.
-
-
- Your repositories:
-
-
-
-{{#repos}}
-
- {{this}}
-
-{{/repos}}
-
-
-
-
+
Create new repository:
-
+
Type:
Maven
@@ -43,7 +25,7 @@ your username is github.com/{{user}}.
Php proxy (remote)
Maven group (virtual)
-
+
diff --git a/src/main/resources/html/repository/edit b/src/main/resources/html/repository/edit
new file mode 100644
index 0000000..3ccd9ba
--- /dev/null
+++ b/src/main/resources/html/repository/edit
@@ -0,0 +1,57 @@
+{{#partial "content"}}
+
+
+ This is YAML config of your repository {{rname}}
+ (read this README file to get help):
+
+{{#unless error}}
+ {{#if result._2}}
+
+ {{{info}}}
+ {{else}}
+ Error: {{result._3}}
+ Status cde: {{result._1}}
+ {{/if}}
+{{else}}
+ Error: {{error}}
+{{/unless}}
+
+{{/partial}}
+{{> base}}
\ No newline at end of file
diff --git a/src/main/resources/html/repository/list b/src/main/resources/html/repository/list
index adc8653..9525e56 100644
--- a/src/main/resources/html/repository/list
+++ b/src/main/resources/html/repository/list
@@ -1,16 +1,24 @@
{{#partial "content"}}
-
- Your repositories:
-
-
-
-{{#repos}}
-
- {{this}}
-
-{{/repos}}
-
+{{#unless error}}
+ {{#if result._2}}
+
+ Your repositories:
+
+
+ {{#result._2}}
+
+ {{this}}
+
+ {{/result._2}}
+
+ {{else}}
+ Error response: {{result._3}}
+ Status code: {{result._1}}
+ {{/if}}
+{{else}}
+ Error: {{error}}
+{{/unless}}
DISCLAIMER :
diff --git a/src/main/resources/html/repository/org/list b/src/main/resources/html/repository/org/list
deleted file mode 100644
index 1a2abbf..0000000
--- a/src/main/resources/html/repository/org/list
+++ /dev/null
@@ -1,29 +0,0 @@
-{{#partial "content"}}
-
-{{#if (not error)}}
-
- Your repositories:
-
-
-
- {{#repos}}
-
- {{this}}
-
- {{/repos}}
-
-{{else}}
- {{error}}
-{{/if}}
-
-
- DISCLAIMER :
- The service provided to you by Artipie is free of charge
- and we expect you to behave good. Don't host here anything aside from
- your private software packages. Too large or abusive files may be deleted without
- notice. We truly hope that you enjoy our service and want it to stay alive.
- Thanks!
-
-
-{{/partial}}
-{{> base}}
diff --git a/src/main/resources/html/repository/result b/src/main/resources/html/repository/result
new file mode 100644
index 0000000..9fd0103
--- /dev/null
+++ b/src/main/resources/html/repository/result
@@ -0,0 +1,18 @@
+{{#partial "content"}}
+
+{{#unless error}}
+ {{#if result._2}}
+ {{result._2}}
+ {{#if redirectUrl}}
+ {{redirectMessage}}
+ {{/if}}
+ {{else}}
+ Error: {{result._3}}
+ Status cde: {{result._1}}
+ {{/if}}
+{{else}}
+ Error: {{error}}
+{{/unless}}
+
+{{/partial}}
+{{> base}}
\ No newline at end of file
diff --git a/src/main/resources/info/conda.info.html b/src/main/resources/info/conda.info.html
new file mode 100644
index 0000000..f55006a
--- /dev/null
+++ b/src/main/resources/info/conda.info.html
@@ -0,0 +1,18 @@
+To install conda packages from Artipie, add repository to conda channels settings to /root/.condarc file (check
+documentation for more details):
+
+channels:
+ - http://central.artipie.com/{{user}}/{{repo}}
+
+Set Artipie repository url for upload to anaconda config:
+
+anaconda config --set url "http://central.artipie.com/{{user}}/{{repo}}" -s
+
+You can also set automatic upload after building package:
+
+conda config --set anaconda_upload yes
+
+
+Now you can install packages from Artipie anaconda repository using conda install
+command and build and upload packages with conda build, or, if the package is already build,
+use anaconda upload command to publish package to Artipie.
\ No newline at end of file
diff --git a/src/main/resources/info/deb.info.html b/src/main/resources/info/deb.info.html
new file mode 100644
index 0000000..a9aa952
--- /dev/null
+++ b/src/main/resources/info/deb.info.html
@@ -0,0 +1,6 @@
+Add local repository to the list of Debian packages for apt by adding
+the following line to the /etc/apt/sources.list:
+
+deb [trusted=yes] http://{{user}}:password@central.artipie.com/{{user}}/{{repo}} {{repo}} main
+
+Then use apt-get as usual.
\ No newline at end of file
diff --git a/src/main/resources/info/docker.info.html b/src/main/resources/info/docker.info.html
new file mode 100644
index 0000000..ea85011
--- /dev/null
+++ b/src/main/resources/info/docker.info.html
@@ -0,0 +1,8 @@
+Tag your image with central.artipie.com/{{user}}/{{repo}} image prefix,
+and push it to central.artipie.com then. E.g.
+for alpine:3.11 use:
+
+docker tag alpine:3.11 central.artipie.com/{{user}}/{{repo}}alpine:3.11
+docker push central.artipie.com/{{user}}/{{repo}}alpine:3.11
+
+
\ No newline at end of file
diff --git a/src/main/resources/info/file-proxy.info.html b/src/main/resources/info/file-proxy.info.html
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/resources/info/file.info.html b/src/main/resources/info/file.info.html
new file mode 100644
index 0000000..3bf5f0c
--- /dev/null
+++ b/src/main/resources/info/file.info.html
@@ -0,0 +1,9 @@
+It's just a binary files repository. Use PUT HTTP request to upload a file,
+and GET for downloading.
+E.g. using HTTPie CLI tool:
+
+# uploading file.bin
+http -a {{user}}:password PUT https://central.artipie.com/{{user}}/{{repo}}file.bin @file.bin
+# downloading file.bin
+http GET https://central.artipie.com/{{user}}/{{repo}}file.bin --output=./file.bin
+
\ No newline at end of file
diff --git a/src/main/resources/info/gem.info.html b/src/main/resources/info/gem.info.html
new file mode 100644
index 0000000..8f8f215
--- /dev/null
+++ b/src/main/resources/info/gem.info.html
@@ -0,0 +1,13 @@
+Before uploading your gems, obtain a key for authorization. A base64 encoded
+login:password would be a valid key:
+
+ export GEM_HOST_API_KEY=$(echo -n "{{user}}:****" | base64)
+
+Now, to upload .gem file into Artipie repository, use gem push command:
+
+ gem push --host https://central.artipie.com/{{user}}/{{repo}}
+
+To install an existing gem, use gem install command:
+
+ gem install awesome_gem --source https://central.artipie.com/{{user}}/{{repo}}
+
\ No newline at end of file
diff --git a/src/main/resources/info/go.info.html b/src/main/resources/info/go.info.html
new file mode 100644
index 0000000..6d4cf91
--- /dev/null
+++ b/src/main/resources/info/go.info.html
@@ -0,0 +1,9 @@
+To use go repository declare the following environment variables:
+
+export GO111MODULE=on
+export GOPROXY="central.artipie.com/{{user}}/{{repo}}"
+
+Now you can install packages directly from Artipie:
+
+go get -x -insecure golang.org/x/time
+
\ No newline at end of file
diff --git a/src/main/resources/info/helm.info.html b/src/main/resources/info/helm.info.html
new file mode 100644
index 0000000..241aa08
--- /dev/null
+++ b/src/main/resources/info/helm.info.html
@@ -0,0 +1,9 @@
+To install chart from Artipie, add repository url to helm and run helm install:
+
+helm repo add artipie https://central.artipie.com/{{user}}/{{repo}}
+helm install my_chart artipie
+
+To upload chart, perform POST request:
+
+ curl -i -X POST --data-binary "@my-chart.tgz" https://central.artipie.com/{{user}}/{{repo}}
+
\ No newline at end of file
diff --git a/src/main/resources/info/maven-group.info.html b/src/main/resources/info/maven-group.info.html
new file mode 100644
index 0000000..38fb038
--- /dev/null
+++ b/src/main/resources/info/maven-group.info.html
@@ -0,0 +1,8 @@
+To use Maven grouped (virtual) repository first create at least two Maven or Maven proxy repositories.
+It can be either just a Maven repository or Maven proxy (mirror) repository. Then specify
+https://central.artipie.com/{{user}}/{{repo}} as distribution management, or
+repositories in pom.xml or as a mirror in settings.xml (see
+more details in concrete repositories documentation). Then group repositories under
+group-settings, pay attention that order does matter: first repository in the list will be
+accessed first, then second, etc. Repository name should be fully formatted, include your username
+prefix, e.g. {{user}}/maven.
\ No newline at end of file
diff --git a/src/main/resources/info/maven-proxy.info.html b/src/main/resources/info/maven-proxy.info.html
new file mode 100644
index 0000000..38717c6
--- /dev/null
+++ b/src/main/resources/info/maven-proxy.info.html
@@ -0,0 +1,18 @@
+Maven proxy repository proxies all request to remote Maven repository appling authentication
+and saves artifacts in Artipie storage cache.
+To use it as a mirror for Apache central, add this configuration to your
+~/.m2/settings.xml:
+
+<settings>
+ [...]
+ <mirrors>
+ <mirror>
+ <id>artipie-mirror</id>
+ <name>Artipie Mirror Repository</name>
+ <url>https://central.artipie.com/{{user}}/{{repo}}</url>
+ <mirrorOf>central</mirrorOf>
+ </mirror>
+ </mirrors>
+</settings>
+
+
\ No newline at end of file
diff --git a/src/main/resources/info/maven.info.html b/src/main/resources/info/maven.info.html
new file mode 100644
index 0000000..78032fc
--- /dev/null
+++ b/src/main/resources/info/maven.info.html
@@ -0,0 +1,31 @@
+With this confirmation,
+the GitHub user @{{user}}
+will be able to publish Maven artifacts and all other users will be able to download.
+
+This is how you may configure it inside your
+pom.xml :
+
+<project>
+ [...]
+ <distributionManagement>
+ <snapshotRepository>
+ <id>artipie</id>
+ <url>https://central.artipie.com/{{user}}/{{repo}}</url>
+ </snapshotRepository>
+ <repository>
+ <id>artipie</id>
+ <url>https://central.artipie.com/{{user}}/{{repo}}</url>
+ </repository>
+ </distributionManagement>
+ <repositories>
+ <repository>
+ <id>artipie</id>
+ <url>https://central.artipie.com/{{user}}/{{repo}}</url>
+ </repository>
+ </repositories>
+</project>
+
+You publish just with
+mvn deploy
+and you download with
+mvn compile .
\ No newline at end of file
diff --git a/src/main/resources/info/npm-proxy.info.html b/src/main/resources/info/npm-proxy.info.html
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/resources/info/npm.info.html b/src/main/resources/info/npm.info.html
new file mode 100644
index 0000000..50be411
--- /dev/null
+++ b/src/main/resources/info/npm.info.html
@@ -0,0 +1,6 @@
+To install or publish npm package into Artipie repository, specify the repository url
+with the --registry option:
+
+ npm publish --registry=https://central.artipie.com/{{user}}/{{repo}}
+ npm install --registry=https://central.artipie.com/{{user}}/{{repo}}
+
\ No newline at end of file
diff --git a/src/main/resources/info/nuget.info.html b/src/main/resources/info/nuget.info.html
new file mode 100644
index 0000000..a4588d8
--- /dev/null
+++ b/src/main/resources/info/nuget.info.html
@@ -0,0 +1,21 @@
+To install or publish NuGet package into Artipie, specify repository url
+and credentials in NuGet.Config xml file:
+
+ <?xml version="1.0" encoding="utf-8"?>
+ <configuration>
+ <packageSources>
+ <add key="artipie-repo" value="https://central.artipie.com/{{user}}/{{repo}}/index.json" />
+ </packageSources>
+ <packageSourceCredentials>
+ <artipie-nuget-test>
+ <add key="Username" value="{{user}}"/>
+ <add key="ClearTextPassword" value="****"/>
+ </artipie-nuget-test>
+ </packageSourceCredentials>
+ </configuration>
+
+Then use this configuration when installing or publishing NuGet packages:
+
+nuget push ./nuget-package -ConfigFile ./NuGet.Config -Verbosity detailed -Source artipie-repo
+nuget install Newtonsoft.Json -Version 12.0.3 -NoCache -ConfigFile ./NuGet.Config -Source artipie-repo
+
\ No newline at end of file
diff --git a/src/main/resources/info/php-proxy.info.html b/src/main/resources/info/php-proxy.info.html
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/resources/info/php.info.html b/src/main/resources/info/php.info.html
new file mode 100644
index 0000000..fe7bdf0
--- /dev/null
+++ b/src/main/resources/info/php.info.html
@@ -0,0 +1,26 @@
+To publish your PHP Composer package create package description JSON file my-package.json with the following content:
+
+{
+ "name": "my-org/my-package",
+ "version": "1.0.0",
+ "dist": {
+ "url": "https://www.my-org.com/files/my-package.1.0.0.zip",
+ "type": "zip"
+ }
+}
+
+And add it to repository using PUT request:
+
+$ curl -X PUT -T 'my-package.json' "http://localhost:8080/my-php"
+
+To use this library in your project add requirement and repository to composer.json:
+
+{
+ "repositories": [
+ {"type": "composer", "url": "http://localhost:8080/my-php"}
+ ],
+ "require": {
+ "my-org/my-package": "1.0.0"
+ }
+}
+
diff --git a/src/main/resources/info/pypi-proxy.info.html b/src/main/resources/info/pypi-proxy.info.html
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/resources/info/pypi.info.html b/src/main/resources/info/pypi.info.html
new file mode 100644
index 0000000..90ee9d5
--- /dev/null
+++ b/src/main/resources/info/pypi.info.html
@@ -0,0 +1,9 @@
+To publish your packages with twine
+specify Artipie repository url with --repository-url option
+
+$ twine upload --repository-url http://central.artipie.com/{{user}}/{{repo}}/ -u {{user}} -p password myproject/dist/*
+
+To install package with pip install specify Artipie repository url with --index-url option:
+
+$ pip install --index-url http://{{user}}:password@central.artipie.com/{{user}}/{{repo}}/ myproject
+
\ No newline at end of file
diff --git a/src/main/resources/info/rpm.info.html b/src/main/resources/info/rpm.info.html
new file mode 100644
index 0000000..ec76bca
--- /dev/null
+++ b/src/main/resources/info/rpm.info.html
@@ -0,0 +1,14 @@
+To install packages from rpm Artipie repository, add the following file
+to yum settings by path /etc/yum.repos.d/example.repo:
+
+[example]
+name=Example Repository
+baseurl=http://{{user}}:password@central.artipie.com/{{user}}/{{repo}}
+enabled=1
+gpgcheck=0
+
+Then list and install packages with specified repository:
+
+yum -y repo-pkgs example list
+yum -y repo-pkgs example install
+
\ No newline at end of file
diff --git a/src/main/resources/template/default.template.yaml b/src/main/resources/template/default.template.yaml
new file mode 100644
index 0000000..9fac2fc
--- /dev/null
+++ b/src/main/resources/template/default.template.yaml
@@ -0,0 +1,9 @@
+repo:
+ type: {{type}}
+ storage: default
+ permissions:
+ {{user}}:
+ - upload
+ - download
+ "*":
+ - download
diff --git a/src/main/resources/template/maven-group.template.yaml b/src/main/resources/template/maven-group.template.yaml
new file mode 100644
index 0000000..1a38a61
--- /dev/null
+++ b/src/main/resources/template/maven-group.template.yaml
@@ -0,0 +1,14 @@
+repo:
+ type: maven-group
+ storage: default
+ settings:
+ repositories:
+ - {{user}}/mprivate1
+ - {{user}}/mprivate2
+ - {{user}}/mproxy
+ permissions:
+ {{user}}:
+ - upload
+ - download
+ "*":
+ - download
diff --git a/src/main/resources/template/maven-proxy.template.yaml b/src/main/resources/template/maven-proxy.template.yaml
new file mode 100644
index 0000000..5112363
--- /dev/null
+++ b/src/main/resources/template/maven-proxy.template.yaml
@@ -0,0 +1,14 @@
+repo:
+ type: maven-proxy
+ storage: default
+ # proxy for Apache Maven central
+ remotes:
+ - uri: https://repo.maven.apache.org/maven2
+ cache:
+ storage: default
+ permissions:
+ {{user}}:
+ - upload
+ - download
+ "*":
+ - download
From 321ba06b747b26f435f54ce2d28b660345c0fbad Mon Sep 17 00:00:00 2001
From: Andrey Popov
Date: Mon, 24 Oct 2022 11:57:03 +0300
Subject: [PATCH 2/2] add standard artipie authentication
---
src/main/java/com/artipie/front/Service.java | 9 ++-
.../java/com/artipie/front/ui/PostSignIn.java | 60 ++++++++++++++++---
2 files changed, 59 insertions(+), 10 deletions(-)
diff --git a/src/main/java/com/artipie/front/Service.java b/src/main/java/com/artipie/front/Service.java
index bf495c8..2d19979 100644
--- a/src/main/java/com/artipie/front/Service.java
+++ b/src/main/java/com/artipie/front/Service.java
@@ -287,8 +287,13 @@ void start(final int port) {
"", MimeTypes.Type.APPLICATION_JSON.asString(),
new SignInPage(), engine
);
- final AuthService auth = new AuthService(this.settings.artipieEnpoint());
- this.ignite.post("", new PostSignIn(auth));
+ this.ignite.post(
+ "",
+ new PostSignIn(
+ new AuthService(this.settings.artipieEnpoint()),
+ AuthByPassword.withCredentials(creds)
+ )
+ );
}
);
this.ignite.path(
diff --git a/src/main/java/com/artipie/front/ui/PostSignIn.java b/src/main/java/com/artipie/front/ui/PostSignIn.java
index f6e586a..6b0a72a 100644
--- a/src/main/java/com/artipie/front/ui/PostSignIn.java
+++ b/src/main/java/com/artipie/front/ui/PostSignIn.java
@@ -4,9 +4,11 @@
*/
package com.artipie.front.ui;
+import com.artipie.front.auth.AuthByPassword;
import com.artipie.front.rest.AuthService;
import io.vavr.Tuple3;
import java.util.Objects;
+import java.util.Optional;
import org.eclipse.jetty.http.HttpStatus;
import spark.Request;
import spark.Response;
@@ -21,6 +23,11 @@
*/
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
public final class PostSignIn implements Route {
+ /**
+ * Password authenticator.
+ */
+ private final AuthByPassword authpwd;
+
/**
* Auth service.
*/
@@ -28,11 +35,12 @@ public final class PostSignIn implements Route {
/**
* New signin form processor.
- *
* @param auth Auth service.
+ * @param authpwd Password authentication
*/
- public PostSignIn(final AuthService auth) {
+ public PostSignIn(final AuthService auth, final AuthByPassword authpwd) {
this.auth = auth;
+ this.authpwd = authpwd;
}
@Override
@@ -40,6 +48,17 @@ public Object handle(final Request req, final Response rsp) throws Exception {
if (req.session() == null) {
Spark.halt(HttpStatus.BAD_REQUEST_400, "session is empty");
}
+ PostSignIn.checkCrsf(req);
+ this.authenticate(req);
+ this.receiveToken(req, rsp);
+ return "Ok";
+ }
+
+ /**
+ * Check crsf.
+ * @param req Request.
+ */
+ private static void checkCrsf(final Request req) {
final String crsf = req.session().attribute("crsf");
req.session().removeAttribute("crsf");
final var valid = Objects.equals(
@@ -48,24 +67,49 @@ public Object handle(final Request req, final Response rsp) throws Exception {
if (!valid) {
Spark.halt(HttpStatus.BAD_REQUEST_400, "CRSF validation failed");
}
- String result = null;
+ }
+
+ /**
+ * Authenticate by username and password.
+ * Redirect to '/dashboard' if JWT-token successful received.
+ * @param req Request.
+ */
+ private void authenticate(final Request req) {
+ final Optional uid = this.authpwd.authenticate(
+ req.queryParamOrDefault("username", ""),
+ req.queryParamOrDefault("password", "")
+ );
+ uid.ifPresentOrElse(
+ val -> {
+ req.session().attribute("uid", val);
+ req.session().attribute("uname", req.queryParamOrDefault("username", ""));
+ },
+ () -> Spark.halt(HttpStatus.UNAUTHORIZED_401, "bad credentials")
+ );
+ }
+
+ /**
+ * Receives JWT-token.
+ * @param req Request.
+ * @param rsp Response.
+ */
+ private void receiveToken(final Request req, final Response rsp) {
final Tuple3 res = this.auth.getJwtToken(
req.queryParamOrDefault("username", ""),
req.queryParamOrDefault("password", "")
);
if (res._3 == null) {
- req.session().attribute("uid", "");
- req.session().attribute("uname", req.queryParamOrDefault("username", ""));
req.session().attribute("token", res._2);
rsp.redirect("/dashboard");
- result = "OK";
} else {
if (res._1 == HttpStatus.UNAUTHORIZED_401) {
- Spark.halt(HttpStatus.UNAUTHORIZED_401, "bad credentials");
+ Spark.halt(
+ HttpStatus.UNAUTHORIZED_401,
+ "Cannot receive JWT-token: bad credentials"
+ );
} else {
Spark.halt(res._1, res._3);
}
}
- return result;
}
}