diff --git a/examples/pom.xml b/examples/pom.xml index fa89b284..9acf019c 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -50,7 +50,6 @@ openapi openapi-tools security - todo-app translator-app webserver integrations diff --git a/examples/todo-app/README.md b/examples/todo-app/README.md deleted file mode 100644 index bdaae1f0..00000000 --- a/examples/todo-app/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# TODO Demo Application - -This application implements todomvc[http://todomvc.com] with two microservices -implemented with Helidon MP and Helidon SE. - -## Build - -```shell -mvn clean package -docker build -t helidon-examples-todo-cassandra cassandra -``` - -## Run - -```shell -docker run -d -p 9042:9042 --name helidon-examples-todo-cassandra helidon-examples-todo-cassandra -docker run --name zipkin -d -p 9411:9411 openzipkin/zipkin -java -jar backend/target/helidon-examples-todo-backend.jar & -java -jar frontend/target/helidon-examples-todo-frontend.jar & -``` - -- Open http://localhost:8080 in your browser -- Login with a Google account -- Add some TODO entries -- Check-out the traces at http://localhost:9411 - -### HTTP proxy - -If you want to run behind an HTTP proxy: - -```shell -export security_providers_0_google_dash_login_proxy_dash_host=proxy.acme.com -export security_providers_0_google_dash_login_proxy_dash_port=80 -``` - -## Stop - -```shell -kill %1 %2 -docker rm -f zipkin helidon-examples-todo-cassandra -``` diff --git a/examples/todo-app/backend/pom.xml b/examples/todo-app/backend/pom.xml deleted file mode 100644 index 47990769..00000000 --- a/examples/todo-app/backend/pom.xml +++ /dev/null @@ -1,161 +0,0 @@ - - - - - 4.0.0 - - io.helidon.applications - helidon-mp - 2.6.8-SNAPSHOT - - - io.helidon.examples.todos - helidon-examples-todo-backend - 1.0.0-SNAPSHOT - Helidon Examples TODO Demo Backend - - - Back-end part of the application uses Helidon MP - - - - io.helidon.demo.todos.backend.Main - 3.10.2 - 4.3.1.0 - 4.9.0 - 4.9.0 - 3.0.2 - 1.32 - - - - - - com.datastax.cassandra - cassandra-driver-core - ${version.lib.cassandra} - - - io.dropwizard.metrics - metrics-core - - - - - org.yaml - snakeyaml - ${version.lib.snakeyaml.override} - - - - - - - io.helidon.microprofile.bundles - helidon-microprofile-core - - - io.helidon.microprofile - helidon-microprofile-security - - - io.helidon.security.providers - helidon-security-providers-google-login - - - io.helidon.security.providers - helidon-security-providers-http-sign - - - io.helidon.security.providers - helidon-security-providers-abac - - - io.helidon.microprofile.tracing - helidon-microprofile-tracing - - - io.helidon.tracing - helidon-tracing-zipkin - - - com.datastax.cassandra - cassandra-driver-core - - - org.junit.jupiter - junit-jupiter-api - test - - - io.helidon.microprofile.tests - helidon-microprofile-tests-junit5 - test - - - org.cassandraunit - cassandra-unit - ${version.cassandra.unit} - test - - - com.datastax.oss - java-driver-core - ${version.datastax.driver.core} - test - - - com.datastax.oss - java-driver-query-builder - ${version.datastax.driver.query.builder} - test - - - com.codahale.metrics - metrics-core - ${version.codahale.metrics.core} - test - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy-libs - - - - - org.jboss.jandex - jandex-maven-plugin - - - make-index - - - - - - diff --git a/examples/todo-app/backend/src/main/java/io/helidon/demo/todos/backend/DbService.java b/examples/todo-app/backend/src/main/java/io/helidon/demo/todos/backend/DbService.java deleted file mode 100644 index 5dc28c83..00000000 --- a/examples/todo-app/backend/src/main/java/io/helidon/demo/todos/backend/DbService.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (c) 2017, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.demo.todos.backend; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Supplier; - -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; - -import io.helidon.config.Config; -import io.helidon.security.SecurityException; - -import com.datastax.driver.core.BoundStatement; -import com.datastax.driver.core.Cluster; -import com.datastax.driver.core.PreparedStatement; -import com.datastax.driver.core.ResultSet; -import com.datastax.driver.core.Row; -import com.datastax.driver.core.Session; -import io.opentracing.Span; -import io.opentracing.SpanContext; -import io.opentracing.tag.Tags; -import io.opentracing.util.GlobalTracer; - -/** - * A service showing to access a no-SQL database. - */ -@ApplicationScoped -public class DbService { - - private static final String LIST_QUERY = "select * from backend where user = ? ALLOW FILTERING"; - private static final String GET_QUERY = "select * from backend where id = ?"; - private static final String INSERT_QUERY = "insert into backend (id, user, message, completed, created)" - + " values (?, ?, ?, ?, ?)"; - private static final String UPDATE_QUERY = "update backend set message = ?, completed = ? where id = ? if user = ?"; - private static final String DELETE_QUERY = "delete from backend where id = ?"; - - private final Session session; - private final PreparedStatement listStatement; - private final PreparedStatement getStatement; - private final PreparedStatement insertStatement; - private final PreparedStatement updateStatement; - private final PreparedStatement deleteStatement; - - /** - * Create a new {@code DbService} instance. - * @param config the configuration root - */ - @Inject - public DbService(Config config) { - Cluster.Builder clusterBuilder = Cluster.builder() - .withoutMetrics(); - - Config cConfig = config.get("cassandra"); - cConfig.get("servers").asList(Config.class).stream() - .flatMap(Collection::stream) - .map(server -> server.get("host").asString().get()) - .forEach(clusterBuilder::addContactPoints); - cConfig.get("port").asInt().ifPresent(clusterBuilder::withPort); - - Cluster cluster = clusterBuilder.build(); - session = cluster.connect("backend"); - - listStatement = session.prepare(LIST_QUERY); - getStatement = session.prepare(GET_QUERY); - insertStatement = session.prepare(INSERT_QUERY); - updateStatement = session.prepare(UPDATE_QUERY); - deleteStatement = session.prepare(DELETE_QUERY); - } - - /** - * Invoke the given supplier and wrap it around with a tracing - * {@code Span}. - * @param the supplier return type - * @param tracingSpan the parent span to use - * @param operation the name of the operation - * @param supplier the supplier to invoke - * @return the object returned by the supplier - */ - private static T execute(SpanContext tracingSpan, String operation, Supplier supplier) { - Span span = startSpan(tracingSpan, operation); - - try { - return supplier.get(); - } catch (Exception e) { - Tags.ERROR.set(span, true); - span.log(Map.of("event", "error", "error.object", e)); - throw e; - } finally { - span.finish(); - } - } - - /** - * Utility method to create and start a child span of the given span. - * @param span the parent span - * @param operation the name for the new span - * @return the created span - */ - private static Span startSpan(SpanContext span, String operation) { - return GlobalTracer.get().buildSpan(operation).asChildOf(span).start(); - } - - /** - * Retrieve the TODOs entries from the database. - * @param tracingSpan the tracing span to use - * @param userId the database user id - * @return retrieved entries as {@code Iterable} - */ - Iterable list(SpanContext tracingSpan, String userId) { - return execute(tracingSpan, "cassandra::list", () -> { - BoundStatement bs = listStatement.bind(userId); - ResultSet rs = session.execute(bs); - - List result = new ArrayList<>(); - for (Row r : rs) { - result.add(Todo.fromDb(r)); - } - - return result; - }); - } - - /** - * Get the entry identified by the given ID from the database. - * @param tracingSpan the tracing span to use - * @param id the ID identifying the entry to retrieve - * @param userId the database user id - * @return retrieved entry as {@code Optional} - */ - Optional get(SpanContext tracingSpan, String id, String userId) { - return execute(tracingSpan, "cassandra::get", () -> getNoContext(id, userId)); - } - - /** - * Get the entry identified by the given ID from the database, fails if the - * entry is not associated with the given {@code userId}. - * @param id the ID identifying the entry to retrieve - * @param userId the database user id - * @return retrieved entry as {@code Optional} - */ - private Optional getNoContext(String id, String userId) { - BoundStatement bs = getStatement.bind(id); - ResultSet rs = session.execute(bs); - Row one = rs.one(); - if (null == one) { - return Optional.empty(); - } - Todo result = Todo.fromDb(one); - if (userId.equals(result.getUserId())) { - return Optional.of(result); - } - throw new SecurityException(String.format( - "User %s attempted to read record %s of another user", - userId, id)); - } - - /** - * Update the given entry in the database. - * @param tracingSpan the tracing span to use - * @param entry the entry to update - * @return {@code Optional} of updated entry if the update was successful, - * otherwise an empty {@code Optional} - */ - Optional update(SpanContext tracingSpan, Todo entry) { - return execute(tracingSpan, "cassandra::update", () -> { - //update backend set message = ? - // , completed = ? where id = ? if user = ? - BoundStatement bs = updateStatement.bind( - entry.getTitle(), - entry.getCompleted(), - entry.getId(), - entry.getUserId()); - ResultSet execute = session.execute(bs); - - if (execute.wasApplied()) { - return Optional.of(entry); - } else { - return Optional.empty(); - } - }); - } - - /** - * Delete the entry identified by the given ID in from the database. - * @param tracingSpan the tracing span to use - * @param id the ID identifying the entry to delete - * @param userId the database user id - * @return the deleted entry as {@code Optional} - */ - Optional delete(SpanContext tracingSpan, String id, String userId) { - return execute(tracingSpan, "cassandra::delete", - () -> getNoContext(id, userId) - .map(todo -> { - BoundStatement bs = deleteStatement.bind(id); - ResultSet rs = session.execute(bs); - if (!rs.wasApplied()) { - throw new RuntimeException("Failed to delete todo: " - + todo); - } - return todo; - })); - } - - /** - * Insert a new entry in the database. - * @param tracingSpan the tracing span to use - * @param entry the entry to insert - */ - void insert(SpanContext tracingSpan, Todo entry) { - execute(tracingSpan, "cassandra::insert", () -> { - BoundStatement bs = insertStatement - .bind(entry.getId(), - entry.getUserId(), - entry.getTitle(), - entry.getCompleted(), - new Date(entry.getCreated())); - - ResultSet execute = session.execute(bs); - if (!execute.wasApplied()) { - throw new RuntimeException("Failed to insert todo: " + entry); - } - return null; - }); - } -} diff --git a/examples/todo-app/backend/src/main/java/io/helidon/demo/todos/backend/JaxRsBackendResource.java b/examples/todo-app/backend/src/main/java/io/helidon/demo/todos/backend/JaxRsBackendResource.java deleted file mode 100644 index d0238b9d..00000000 --- a/examples/todo-app/backend/src/main/java/io/helidon/demo/todos/backend/JaxRsBackendResource.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (c) 2017, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.demo.todos.backend; - -import java.util.Collections; -import java.util.UUID; - -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; -import javax.json.Json; -import javax.json.JsonArrayBuilder; -import javax.json.JsonBuilderFactory; -import javax.json.JsonObject; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -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 javax.ws.rs.core.Response; - -import io.helidon.security.Principal; -import io.helidon.security.SecurityContext; -import io.helidon.security.Subject; -import io.helidon.security.annotations.Authenticated; -import io.helidon.security.annotations.Authorized; - -import io.opentracing.Tracer; -import org.eclipse.microprofile.opentracing.Traced; - -/** - * The TODO backend REST service. - */ -@Path("/api/backend") -@Authenticated -@Authorized -@ApplicationScoped -public class JaxRsBackendResource { - - private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); - - private final DbService backendService; - private final Tracer tracer; - - /** - * Create new {@code JaxRsBackendResource} instance. - * @param dbs the database service facade to use - * @param tracer tracer to use - */ - @Inject - public JaxRsBackendResource(DbService dbs, Tracer tracer) { - this.backendService = dbs; - this.tracer = tracer; - } - - /** - * Retrieve all TODO entries. - * - * @param context security context to map the user - * @return the response with the retrieved entries as entity - */ - @GET - @Produces(MediaType.APPLICATION_JSON) - @Traced(operationName = "jaxrs:list") - public Response list(@Context SecurityContext context) { - JsonArrayBuilder builder = JSON.createArrayBuilder(); - backendService.list(tracer.activeSpan().context(), getUserId(context)) - .forEach(data -> builder.add(data.forRest())); - return Response.ok(builder.build()).build(); - } - - /** - * Get the TODO entry identified by the given ID. - * @param id the ID of the entry to retrieve - * @param context security context to map the user - * @return the response with the retrieved entry as entity - */ - @GET - @Path("/{id}") - @Produces(MediaType.APPLICATION_JSON) - public Response get(@PathParam("id") String id, @Context SecurityContext context) { - - return backendService - .get(tracer.activeSpan().context(), id, getUserId(context)) - .map(Todo::forRest) - .map(Response::ok) - .orElse(Response.status(Response.Status.NOT_FOUND)) - .build(); - } - - /** - * Delete the TODO entry identified by the given ID. - * @param id the id of the entry to delete - * @param context security context to map the user - * @return the response with the deleted entry as entity - */ - @DELETE - @Path("/{id}") - @Produces(MediaType.APPLICATION_JSON) - public Response delete(@PathParam("id") String id, @Context SecurityContext context) { - - return backendService - .delete(tracer.activeSpan().context(), id, getUserId(context)) - .map(Todo::forRest) - .map(Response::ok) - .orElse(Response.status(Response.Status.NOT_FOUND)) - .build(); - } - - /** - * Create a new TODO entry. - * @param jsonObject the value of the new entry - * @param context security context to map the user - * @return the response ({@code 200} status if successful - */ - @POST - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public Response createIt(JsonObject jsonObject, @Context SecurityContext context) { - - String newId = UUID.randomUUID().toString(); - String userId = getUserId(context); - Todo newBackend = Todo.newTodoFromRest(jsonObject, userId, newId); - - backendService.insert(tracer.activeSpan().context(), newBackend); - - return Response.ok(newBackend.forRest()).build(); - } - - /** - * Update the TODO entry identified by the given ID. - * @param id the ID of the entry to update - * @param jsonObject the updated value of the entry - * @param context security context to map the user - * @return the response with the updated entry as entity - */ - @PUT - @Path("/{id}") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public Response update(@PathParam("id") String id, JsonObject jsonObject, @Context SecurityContext context) { - return backendService - .update(tracer.activeSpan().context(), Todo.fromRest(jsonObject, getUserId(context), id)) - .map(Todo::forRest) - .map(Response::ok) - .orElse(Response.status(Response.Status.NOT_FOUND)) - .build(); - } - - /** - * Get the user id from the security context. - * @param context the security context - * @return user id found in the context or {@code } otherwise - */ - private String getUserId(SecurityContext context) { - return context.user() - .map(Subject::principal) - .map(Principal::id) - .orElse(""); - } -} diff --git a/examples/todo-app/backend/src/main/java/io/helidon/demo/todos/backend/Main.java b/examples/todo-app/backend/src/main/java/io/helidon/demo/todos/backend/Main.java deleted file mode 100644 index 584b7d26..00000000 --- a/examples/todo-app/backend/src/main/java/io/helidon/demo/todos/backend/Main.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2017, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.demo.todos.backend; - -import java.util.List; - -import io.helidon.common.LogConfig; -import io.helidon.config.Config; -import io.helidon.microprofile.server.Server; - -import static io.helidon.config.ConfigSources.classpath; -import static io.helidon.config.ConfigSources.environmentVariables; -import static io.helidon.config.ConfigSources.file; - -/** - * Main class to start the service. - */ -public final class Main { - - /** - * Cannot be instantiated. - */ - private Main() { - } - - /** - * Application main entry point. - * - * @param args command line arguments - */ - public static void main(final String[] args) { - - // load logging configuration - LogConfig.configureRuntime(); - - Config config = buildConfig(); - - // as we need to use custom filter - // we need to build Server with custom config - Server server = Server.builder() - .config(config) - .build(); - - server.start(); - } - - /** - * Load the configuration from all sources. - * @return the configuration root - */ - static Config buildConfig() { - return Config.builder() - .sources(List.of( - environmentVariables(), - // expected on development machine - // to override props for dev - file("dev.yaml").optional(), - // expected in k8s runtime - // to configure testing/production values - file("prod.yaml").optional(), - // in jar file - // (see src/main/resources/application.yaml) - classpath("application.yaml"))) - // support for passwords in configuration - //.addFilter(SecureConfigFilter.fromConfig()) - .build(); - } -} diff --git a/examples/todo-app/backend/src/main/java/io/helidon/demo/todos/backend/Todo.java b/examples/todo-app/backend/src/main/java/io/helidon/demo/todos/backend/Todo.java deleted file mode 100644 index d1672195..00000000 --- a/examples/todo-app/backend/src/main/java/io/helidon/demo/todos/backend/Todo.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright (c) 2017, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.demo.todos.backend; - -import java.time.Instant; -import java.time.format.DateTimeFormatter; -import java.util.Collections; -import java.util.UUID; - -import javax.json.Json; -import javax.json.JsonBuilderFactory; -import javax.json.JsonNumber; -import javax.json.JsonObject; -import javax.json.JsonObjectBuilder; - -import com.datastax.driver.core.Row; - -/** - * Data object for backend. - */ -public final class Todo { - - /** - * Date formatter to format the dates of the TODO entries. - */ - private static final DateTimeFormatter DATE_FORMAT = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSVV"); - - /** - * Factory for creating JSON builders. - */ - private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); - - /** - * The TODO ID. - */ - private String id; - - /** - * The user ID associated with this TODO. - */ - private String userId; - - /** - * The TODO title. - */ - private String title; - - /** - * The TODO completed flag. - */ - private Boolean completed; - - /** - * The TODO creation timestamp. - */ - private long created; - - /** - * Create a new {@code Todo} instance from a database entry in JSON format. - * @param jsonObject the database entry - * @return the created instance - */ - public static Todo fromDb(final JsonObject jsonObject) { - - Todo result = new Todo(); - result.id = jsonObject.getString("id"); - result.userId = jsonObject.getString("user"); - result.title = jsonObject.getString("message"); - result.completed = jsonObject.getBoolean("completed"); - result.created = Instant.from(DATE_FORMAT - .parse(jsonObject.getString("created"))).toEpochMilli(); - return result; - } - - /** - * Create a new {@code Todo} instance from a REST entry. - * The created entry will be new, i.e the {@code completed} flag will be set - * to {@code false} and the {@code created} timestamp set to the current - * time. - * @param jsonObject the REST entry - * @param userId the user ID associated with this entry - * @param id the entry ID - * @return the created instance - */ - public static Todo newTodoFromRest(final JsonObject jsonObject, - final String userId, - final String id) { - - Todo result = new Todo(); - result.id = id; - result.userId = userId; - result.title = jsonObject.getString("title"); - result.completed = jsonObject.getBoolean("completed", false); - result.created = System.currentTimeMillis(); - return result; - } - - /** - * Create a new {@code Todo} instance from a REST entry. - * @param jsonObject the REST entry - * @param userId the user ID associated with this entry - * @param id the entry ID - * @return the created instance - */ - public static Todo fromRest(final JsonObject jsonObject, - final String userId, - final String id) { - - Todo result = new Todo(); - result.id = id; - result.userId = userId; - result.title = jsonObject.getString("title", ""); - result.completed = jsonObject.getBoolean("completed"); - JsonNumber created = jsonObject.getJsonNumber("created"); - if (null != created) { - result.created = created.longValue(); - } - return result; - } - - /** - * Create a new {@code Todo} instance from a database entry. - * @param row the database entry - * @return the created instance - */ - public static Todo fromDb(final Row row) { - - Todo result = new Todo(); - result.id = row.getString("id"); - result.userId = row.getString("user"); - result.title = row.getString("message"); - result.completed = row.getBool("completed"); - result.created = row.getTimestamp("created").getTime(); - return result; - } - - /** - * Create a new {@code Todo} instance. - * The created entry will be new, i.e the {@code completed} flag will be set - * to {@code false} and the {@code created} timestamp set to the current - * time. - * @param userId the user ID associated with the new entry - * @param title the title for the new entry - * @return the created instance - */ - public static Todo create(final String userId, final String title) { - Todo result = new Todo(); - - result.id = UUID.randomUUID().toString(); - result.userId = userId; - result.title = title; - result.completed = false; - result.created = System.currentTimeMillis(); - - return result; - } - - /** - * Convert this {@code Todo} instance to the JSON database format. - * @return {@code JsonObject} - */ - public JsonObject forDb() { - //to store to DB - JsonObjectBuilder builder = JSON.createObjectBuilder(); - return builder.add("id", id) - .add("user", userId) - .add("message", title) - .add("completed", completed) - .add("created", created) - .build(); - } - - /** - * Convert this {@code Todo} instance to the JSON REST format. - * @return {@code JsonObject} - */ - public JsonObject forRest() { - //to send over to rest - JsonObjectBuilder builder = JSON.createObjectBuilder(); - return builder.add("id", id) - .add("user", userId) - .add("title", title) - .add("completed", completed) - .add("created", created) - .build(); - } - - /** - * Get the TODO ID. - * @return the {@code String} identifying this entry - */ - public String getId() { - return id; - } - - /** - * Get the user ID associated with this TODO. - * @return the {@code String} identifying the user - */ - public String getUserId() { - return userId; - } - - /** - * Get the TODO title. - * @return title - */ - public String getTitle() { - return title; - } - - /** - * Get the completed flag. - * @return completed flag. - */ - public Boolean getCompleted() { - return completed; - } - - /** - * Set the completed flag. - * @param iscomplete the completed flag value - */ - public void setCompleted(final boolean iscomplete) { - this.completed = iscomplete; - } - - /** - * Get the creation timestamp. - * @return timestamp - */ - public long getCreated() { - return created; - } - - @Override - public String toString() { - return "Todo{" - + "id='" + id + '\'' - + ", userId='" + userId + '\'' - + ", title='" + title + '\'' - + ", completed=" + completed - + ", created=" + created - + '}'; - } -} diff --git a/examples/todo-app/backend/src/main/java/io/helidon/demo/todos/backend/package-info.java b/examples/todo-app/backend/src/main/java/io/helidon/demo/todos/backend/package-info.java deleted file mode 100644 index 111b64f6..00000000 --- a/examples/todo-app/backend/src/main/java/io/helidon/demo/todos/backend/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2017, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * TODOs Demo application backend. - */ -package io.helidon.demo.todos.backend; diff --git a/examples/todo-app/backend/src/main/resources/META-INF/beans.xml b/examples/todo-app/backend/src/main/resources/META-INF/beans.xml deleted file mode 100644 index 7075ac07..00000000 --- a/examples/todo-app/backend/src/main/resources/META-INF/beans.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - diff --git a/examples/todo-app/backend/src/main/resources/application.yaml b/examples/todo-app/backend/src/main/resources/application.yaml deleted file mode 100644 index 8727766d..00000000 --- a/examples/todo-app/backend/src/main/resources/application.yaml +++ /dev/null @@ -1,48 +0,0 @@ -# -# Copyright (c) 2018, 2024 Oracle and/or its affiliates. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -env: docker - -server: - port: 8854 - host: 0.0.0.0 - -tracing: - service: "todo:back" - port: 9411 - -cassandra: - port: 9042 - servers: - - host: "localhost" - -security: - config: - require-encryption: false - aes.insecure-passphrase: "changeit" - provider-policy: - type: "COMPOSITE" - authentication: - - name: "google-login" - - name: "http-signatures" - providers: - - google-login: - client-id: "1048216952820-6a6ke9vrbjlhngbc0al0dkj9qs9tqbk2.apps.googleusercontent.com" - - abac: - - http-signatures: - inbound.keys: - - key-id: "frontend" - principal-name: "Frontend Service" - hmac.secret: "${CLEAR=changeit}" diff --git a/examples/todo-app/backend/src/main/resources/logging.properties b/examples/todo-app/backend/src/main/resources/logging.properties deleted file mode 100644 index 3ed94f67..00000000 --- a/examples/todo-app/backend/src/main/resources/logging.properties +++ /dev/null @@ -1,28 +0,0 @@ -# -# Copyright (c) 2017, 2024 Oracle and/or its affiliates. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -#All attributes details -handlers=io.helidon.common.HelidonConsoleHandler -java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n - -#All log level details -.level=WARNING -io.helidon.webserver.level=INFO -io.helidon.security.level=INFO -io.helidon.tracing.level=FINE -AUDIT.level=FINEST - - diff --git a/examples/todo-app/backend/src/test/java/io/helidon/demo/todos/backend/BackendTests.java b/examples/todo-app/backend/src/test/java/io/helidon/demo/todos/backend/BackendTests.java deleted file mode 100644 index 52a2d5a0..00000000 --- a/examples/todo-app/backend/src/test/java/io/helidon/demo/todos/backend/BackendTests.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2021, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.demo.todos.backend; - -import java.io.IOException; -import java.util.Base64; -import java.util.Properties; - -import javax.inject.Inject; -import javax.json.Json; -import javax.json.JsonArray; -import javax.json.JsonObject; -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.MediaType; - -import io.helidon.common.http.Http; -import io.helidon.config.mp.MpConfigSources; -import io.helidon.config.yaml.mp.YamlMpConfigSource; -import io.helidon.microprofile.tests.junit5.Configuration; -import io.helidon.microprofile.tests.junit5.HelidonTest; - -import com.datastax.driver.core.Cluster; -import com.datastax.driver.core.Session; -import org.cassandraunit.utils.EmbeddedCassandraServerHelper; -import org.eclipse.microprofile.config.spi.ConfigProviderResolver; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -@HelidonTest -@Configuration(useExisting = true) -class BackendTests { - - private final static String CASSANDRA_HOST = "127.0.0.1"; - - @Inject - private WebTarget webTarget; - - @BeforeAll - static void init() throws IOException { - Properties cassandraProperties = initCassandra(); - - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - ConfigProviderResolver configResolver = ConfigProviderResolver.instance(); - - org.eclipse.microprofile.config.Config mpConfig = configResolver.getBuilder() - .withSources(YamlMpConfigSource.create(cl.getResource("test-application.yaml")), - MpConfigSources.create(cassandraProperties)) - .build(); - - configResolver.registerConfig(mpConfig, null); - } - - @AfterAll - static void stopServer() { - EmbeddedCassandraServerHelper.cleanEmbeddedCassandra(); - } - - private static Properties initCassandra() throws IOException { - EmbeddedCassandraServerHelper.startEmbeddedCassandra(EmbeddedCassandraServerHelper.CASSANDRA_RNDPORT_YML_FILE, - 20000L); - Properties prop = new Properties(); - prop.put("cassandra.port", String.valueOf(EmbeddedCassandraServerHelper.getNativeTransportPort())); - prop.put("cassandra.servers.host.host", CASSANDRA_HOST); - - Cluster cluster = Cluster.builder() - .withoutMetrics() - .addContactPoint(CASSANDRA_HOST) - .withPort(EmbeddedCassandraServerHelper.getNativeTransportPort()) - .build(); - - Session session = cluster.connect(); - session.execute("CREATE KEYSPACE backend WITH REPLICATION = {'class' : 'SimpleStrategy', 'replication_factor' : 1};"); - session.execute( - "CREATE TABLE backend.backend (id ascii, user ascii, message ascii, completed Boolean, created timestamp, " - + "PRIMARY KEY (id));"); - session.execute("select * from backend.backend;"); - - session.close(); - cluster.close(); - - return prop; - } - - @Test - void testTodoScenario() { - String basicAuth = "Basic " + Base64.getEncoder().encodeToString("john:changeit".getBytes()); - JsonObject todo = Json.createObjectBuilder() - .add("title", "todo title") - .build(); - - // Add a new todo - JsonObject returnedTodo = webTarget - .path("/api/backend") - .request(MediaType.APPLICATION_JSON_TYPE) - .header(Http.Header.AUTHORIZATION, basicAuth) - .post(Entity.json(todo), JsonObject.class); - - assertThat(returnedTodo.getString("user"), is("john")); - assertThat(returnedTodo.getString("title"), is(todo.getString("title"))); - - // Get the todo created earlier - JsonObject fromServer = webTarget.path("/api/backend/" + returnedTodo.getString("id")) - .request(MediaType.APPLICATION_JSON_TYPE) - .header(Http.Header.AUTHORIZATION, basicAuth) - .get(JsonObject.class); - - assertThat(fromServer, is(returnedTodo)); - - // Update the todo created earlier - JsonObject updatedTodo = Json.createObjectBuilder() - .add("title", "updated title") - .add("completed", false) - .build(); - - fromServer = webTarget.path("/api/backend/" + returnedTodo.getString("id")) - .request(MediaType.APPLICATION_JSON_TYPE) - .header(Http.Header.AUTHORIZATION, basicAuth) - .put(Entity.json(updatedTodo), JsonObject.class); - - assertThat(fromServer.getString("title"), is(updatedTodo.getString("title"))); - - // Delete the todo created earlier - fromServer = webTarget.path("/api/backend/" + returnedTodo.getString("id")) - .request(MediaType.APPLICATION_JSON_TYPE) - .header(Http.Header.AUTHORIZATION, basicAuth) - .delete(JsonObject.class); - - assertThat(fromServer.getString("id"), is(returnedTodo.getString("id"))); - - // Get list of todos - JsonArray jsonValues = webTarget.path("/api/backend") - .request(MediaType.APPLICATION_JSON_TYPE) - .header(Http.Header.AUTHORIZATION, basicAuth) - .get(JsonArray.class); - - assertThat("There should be no todos on server", jsonValues.size(), is(0)); - } - -} diff --git a/examples/todo-app/backend/src/test/resources/test-application.yaml b/examples/todo-app/backend/src/test/resources/test-application.yaml deleted file mode 100644 index 299eed13..00000000 --- a/examples/todo-app/backend/src/test/resources/test-application.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# -# Copyright (c) 2021, 2024 Oracle and/or its affiliates. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# increase importance -config_ordinal: 500 - -# we use custom config and Helidon JUnit integration, must allow initializer -mp: - initializer: - allow: true - no-warn: true - -server: - port: 0 - host: localhost - -tracing: - service: "todo:back" - enabled: false - -security: - providers: - - http-basic-auth: - realm: "helidon" - users: - - login: "john" - password: "changeit" diff --git a/examples/todo-app/cassandra/Dockerfile b/examples/todo-app/cassandra/Dockerfile deleted file mode 100644 index b41a1b50..00000000 --- a/examples/todo-app/cassandra/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# -# Copyright (c) 2017, 2024 Oracle and/or its affiliates. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -FROM cassandra:3.11.16 - -ADD startup.sh / - -RUN chmod -v u=rx,og-rwx /startup.sh - -ENTRYPOINT ["/startup.sh"] - -EXPOSE 7000 7001 7199 9042 9160 - -CMD ["cassandra", "-f"] diff --git a/examples/todo-app/cassandra/startup.sh b/examples/todo-app/cassandra/startup.sh deleted file mode 100644 index 0cb1765d..00000000 --- a/examples/todo-app/cassandra/startup.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2017, 2024 Oracle and/or its affiliates. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -set -e - -echo 'Starting Cassandra database' -/docker-entrypoint.sh "$@" > /var/log/cassandra.log & - -echo 'Waiting for database to become available' -COUNT='1' -while test $COUNT -lt '120' && ! timeout 1 bash -c 'cat < /dev/null > /dev/tcp/127.0.0.1/9042' > /dev/null 2>&1 ; do - if [ "$((COUNT%10))" -eq '0' ]; then - echo " ...$COUNT s" - fi - sleep 1 - COUNT=$((COUNT+1)) -done - -echo 'Creating todos table' -cqlsh -e " - CREATE KEYSPACE backend WITH REPLICATION = {'class' : 'SimpleStrategy', 'replication_factor' : 1}; - CREATE TABLE backend.backend (id ascii, user ascii, message ascii, completed Boolean, created timestamp, PRIMARY KEY (id)); - select * from backend.backend; -" \ - || true - -echo 'Opening database log file' -echo '-------------------------' -tail -f /var/log/cassandra.log diff --git a/examples/todo-app/frontend/pom.xml b/examples/todo-app/frontend/pom.xml deleted file mode 100644 index 167fee39..00000000 --- a/examples/todo-app/frontend/pom.xml +++ /dev/null @@ -1,159 +0,0 @@ - - - - - 4.0.0 - - io.helidon.applications - helidon-se - 2.6.8-SNAPSHOT - - - io.helidon.examples.todo - helidon-examples-todo-frontend - 1.0.0-SNAPSHOT - Helidon Examples TODO Demo Frontend - - - Front-end part of the application, uses Helidon SE - - - - io.helidon.demo.todos.frontend.Main - ${mainClass} - - - - - io.helidon.common - helidon-common - - - io.helidon.webserver - helidon-webserver - - - io.helidon.webserver - helidon-webserver-static-content - - - io.helidon.webclient - helidon-webclient - - - io.helidon.webclient - helidon-webclient-security - - - io.helidon.webclient - helidon-webclient-tracing - - - io.helidon.webserver - helidon-webserver-access-log - - - io.helidon.media - helidon-media-jsonp - - - io.helidon.tracing - helidon-tracing - - - io.helidon.tracing - helidon-tracing-zipkin - - - io.helidon.config - helidon-config - - - io.helidon.config - helidon-config-yaml - - - io.helidon.config - helidon-config-encryption - - - io.helidon.security - helidon-security - - - io.helidon.security.providers - helidon-security-providers-google-login - - - io.helidon.security.providers - helidon-security-providers-abac - - - io.helidon.security.providers - helidon-security-providers-http-sign - - - io.helidon.security.integration - helidon-security-integration-webserver - - - io.helidon.metrics - helidon-metrics-api - - - io.helidon.metrics - helidon-metrics-service-api - - - io.helidon.metrics - helidon-metrics - - - io.helidon.security.providers - helidon-security-providers-http-auth - test - - - org.junit.jupiter - junit-jupiter-api - test - - - org.hamcrest - hamcrest-all - test - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy-libs - - - - - - diff --git a/examples/todo-app/frontend/src/main/java/io/helidon/demo/todos/frontend/BackendServiceClient.java b/examples/todo-app/frontend/src/main/java/io/helidon/demo/todos/frontend/BackendServiceClient.java deleted file mode 100644 index c39cea7c..00000000 --- a/examples/todo-app/frontend/src/main/java/io/helidon/demo/todos/frontend/BackendServiceClient.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2017, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.demo.todos.frontend; - -import java.util.function.Function; - -import javax.json.JsonArray; -import javax.json.JsonObject; - -import io.helidon.common.http.Http.ResponseStatus.Family; -import io.helidon.common.reactive.Single; -import io.helidon.config.Config; -import io.helidon.media.jsonp.JsonpSupport; -import io.helidon.webclient.WebClient; -import io.helidon.webclient.WebClientResponse; -import io.helidon.webclient.security.WebClientSecurity; -import io.helidon.webclient.tracing.WebClientTracing; -import io.helidon.webserver.HttpException; - -/** - * Client to invoke the backend service. - */ -final class BackendServiceClient { - - private final WebClient client; - - BackendServiceClient(Config config) { - String serviceEndpoint = config.get("services.backend.endpoint").asString().get(); - this.client = WebClient.builder() - .useSystemServiceLoader(false) - .addService(WebClientTracing.create()) - .addService(WebClientSecurity.create()) - .addMediaSupport(JsonpSupport.create()) - .baseUri(serviceEndpoint + "/api/backend").build(); - } - - /** - * Retrieve all entries from the backend. - * - * @return single with all records - */ - Single list() { - return client.get() - .request() - .flatMapSingle(processResponse(JsonArray.class)); - } - - /** - * Retrieve the entry identified by the given ID. - * - * @param id the ID identifying the entry to retrieve - * @return retrieved entry as a {@code JsonObject} - */ - Single get(String id) { - return client.get() - .path(id) - .request() - .flatMapSingle(processResponse(JsonObject.class)); - } - - /** - * Delete the entry identified by the given ID. - * - * @param id the ID identifying the entry to delete - * @return deleted entry as a {@code JsonObject} - */ - Single deleteSingle(String id) { - return client.delete() - .path(id) - .request() - .flatMapSingle(processResponse(JsonObject.class)); - } - - /** - * Create a new entry. - * - * @param json the new entry value to create as {@code JsonObject} - * @return created entry as {@code JsonObject} - */ - Single create(JsonObject json) { - return client.post() - .submit(json) - .flatMapSingle(processResponse(JsonObject.class)); - } - - /** - * Update an entry identified by the given ID. - * - * @param id the ID identifying the entry to update - * @param json the update entry value as {@code JsonObject} - * @return updated entry as {@code JsonObject} - */ - Single update(String id, JsonObject json) { - return client.put() - .path(id) - .submit(json) - .flatMapSingle(processResponse(JsonObject.class)); - } - - private Function> processResponse(Class clazz) { - return response -> { - if (response.status().family() != Family.SUCCESSFUL) { - return Single.error(new HttpException("backend error", response.status())); - } - return response.content().as(clazz); - }; - } -} diff --git a/examples/todo-app/frontend/src/main/java/io/helidon/demo/todos/frontend/Main.java b/examples/todo-app/frontend/src/main/java/io/helidon/demo/todos/frontend/Main.java deleted file mode 100644 index 534e5798..00000000 --- a/examples/todo-app/frontend/src/main/java/io/helidon/demo/todos/frontend/Main.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2017, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.demo.todos.frontend; - -import java.time.Duration; -import java.util.List; - -import io.helidon.common.LogConfig; -import io.helidon.common.http.Http; -import io.helidon.config.Config; -import io.helidon.config.FileSystemWatcher; -import io.helidon.media.jsonp.JsonpSupport; -import io.helidon.metrics.serviceapi.MetricsSupport; -import io.helidon.security.Security; -import io.helidon.security.integration.webserver.WebSecurity; -import io.helidon.tracing.TracerBuilder; -import io.helidon.webserver.Routing; -import io.helidon.webserver.WebServer; -import io.helidon.webserver.accesslog.AccessLogSupport; -import io.helidon.webserver.staticcontent.StaticContentSupport; - -import io.opentracing.Tracer; - -import static io.helidon.config.ConfigSources.classpath; -import static io.helidon.config.ConfigSources.environmentVariables; -import static io.helidon.config.ConfigSources.file; -import static io.helidon.config.PollingStrategies.regular; - -/** - * Main class to start the service. - */ -public final class Main { - - /** - * Interval for config polling. - */ - private static final Long POLLING_INTERVAL = 5L; - - /** - * Cannot be instantiated. - */ - private Main() { - } - - /** - * Application main entry point. - * - * @param args command line arguments - */ - public static void main(final String[] args) { - - // load logging configuration - LogConfig.configureRuntime(); - - Config config = buildConfig(); - - Security security = Security.create(config.get("security")); - - // create a web server - WebServer server = WebServer.builder() - .routing(createRouting(security, config)) - .config(config.get("webserver")) - .addMediaSupport(JsonpSupport.create()) - .tracer(registerTracer(config)) - .build(); - - // start the web server - server.start().whenComplete(Main::started); - } - - /** - * Create a {@code Tracer} instance using the given {@code Config}. - * @param config the configuration root - * @return the created {@code Tracer} - */ - private static Tracer registerTracer(Config config) { - return TracerBuilder.create(config.get("tracing")).build(); - } - - /** - * Create the web server routing and register all handlers. - * @param security the security features - * @param config the configuration root - * @return the created {@code Routing} - */ - private static Routing createRouting(Security security, Config config) { - return Routing.builder() - .register(AccessLogSupport.create()) - // register metrics features (on "/metrics") - .register(MetricsSupport.create()) - // register security features - .register(WebSecurity.create(security, config.get("security"))) - // redirect POST / to GET / - .post("/", (req, res) -> { - res.addHeader(Http.Header.LOCATION, "/"); - res.status(Http.Status.SEE_OTHER_303); - res.send(); - }) - // register static content support (on "/") - .register(StaticContentSupport.builder("/WEB").welcomeFileName("index.html")) - // register API handler (on "/api") - this path is secured (see application.yaml) - .register("/api", new TodoService(new BackendServiceClient(config))) - .build(); - } - - /** - * Handle web server started event: if successful print server started - * message in the console with the corresponding URL, otherwise print an - * error message and exit the application. - * @param webServer the {@code WebServer} instance - * @param throwable if non {@code null}, indicate a server startup error - */ - private static void started(WebServer webServer, Throwable throwable) { - if (throwable == null) { - System.out.println("WEB server is up! http://localhost:" + webServer.port()); - } else { - throwable.printStackTrace(System.out); - System.exit(1); - } - } - - /** - * Load the configuration from all sources. - * @return the configuration root - */ - private static Config buildConfig() { - return Config.builder() - .sources(List.of( - environmentVariables(), - // expected on development machine - // to override props for dev - file("dev.yaml") - .changeWatcher(FileSystemWatcher.create()) - .optional(), - // expected in k8s runtime - // to configure testing/production values - file("prod.yaml") - .pollingStrategy(regular(Duration.ofSeconds(POLLING_INTERVAL))) - .optional(), - // in jar file - // (see src/main/resources/application.yaml) - classpath("application.yaml"))) - .build(); - } -} diff --git a/examples/todo-app/frontend/src/main/java/io/helidon/demo/todos/frontend/TodoService.java b/examples/todo-app/frontend/src/main/java/io/helidon/demo/todos/frontend/TodoService.java deleted file mode 100644 index 26542bd4..00000000 --- a/examples/todo-app/frontend/src/main/java/io/helidon/demo/todos/frontend/TodoService.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (c) 2018, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.helidon.demo.todos.frontend; - -import javax.json.JsonObject; - -import io.helidon.common.http.Http; -import io.helidon.metrics.api.RegistryFactory; -import io.helidon.webserver.Routing; -import io.helidon.webserver.ServerRequest; -import io.helidon.webserver.ServerResponse; -import io.helidon.webserver.Service; - -import org.eclipse.microprofile.metrics.Counter; -import org.eclipse.microprofile.metrics.Metadata; -import org.eclipse.microprofile.metrics.MetricRegistry; -import org.eclipse.microprofile.metrics.MetricType; -import org.eclipse.microprofile.metrics.MetricUnits; - -/** - * TODO service. - *

- * An entry is structured as follows: - * { 'title': string, 'completed': boolean, 'id': string } - *

- * The IDs are server generated on the initial POST operation (so they are not - * included in that case). - *

- * Here is a summary of the operations: - * GET /api/todo: Get all entries - * GET /api/todo/{id}: Get an entry by ID - * POST /api/todo: Create a new entry, created entry is returned - * DELETE /api/todo/{id}: Delete an entry, deleted entry is returned - * PUT /api/todo/{id}: Update an entry, updated entry is returned - */ -public final class TodoService implements Service { - - private final BackendServiceClient bsc; - private final Counter createCounter; - private final Counter updateCounter; - private final Counter deleteCounter; - - /** - * Create a new {@code TodosHandler} instance. - * - * @param bsc the {@code BackendServiceClient} to use - */ - TodoService(BackendServiceClient bsc) { - MetricRegistry registry = RegistryFactory.getInstance().getRegistry(MetricRegistry.Type.APPLICATION); - this.bsc = bsc; - this.createCounter = registry.counter("created"); - this.updateCounter = registry.counter("updates"); - this.deleteCounter = registry.counter(Metadata.builder() - .withName("deletes") - .withDisplayName("deletes") - .withDescription("Number of deleted todos") - .withType(MetricType.COUNTER) - .withUnit(MetricUnits.NONE) - .build()); - } - - @Override - public void update(Routing.Rules rules) { - rules.get("/todo/{id}", this::get) - .delete("/todo/{id}", this::delete) - .put("/todo/{id}", this::update) - .get("/todo", this::list) - .post("/todo", this::create); - } - - /** - * Handler for {@code POST /todo}. - * - * @param req the server request - * @param res the server response - */ - private void create(ServerRequest req, ServerResponse res) { - req.content() - .as(JsonObject.class) - .flatMapSingle(bsc::create) - .peek(ignored -> createCounter.inc()) - .onError(res::send) - .forSingle(json -> { - res.status(Http.Status.CREATED_201); - res.send(json); - }); - } - - /** - * Handler for {@code GET /todo}. - * - * @param req the server request - * @param res the server response - */ - private void list(ServerRequest req, ServerResponse res) { - bsc.list() - .onError(res::send) - .forSingle(res::send); - } - - /** - * Handler for {@code PUT /todo/id}. - * - * @param req the server request - * @param res the server response - */ - private void update(ServerRequest req, ServerResponse res) { - req.content() - .as(JsonObject.class) - .flatMapSingle(json -> bsc.update(req.path().param("id"), json)) - .peek(ignored -> updateCounter.inc()) - .onError(res::send) - .forSingle(res::send); - } - - /** - * Handler for {@code DELETE /todo/id}. - * - * @param req the server request - * @param res the server response - */ - private void delete(ServerRequest req, ServerResponse res) { - bsc.deleteSingle(req.path().param("id")) - .peek(ignored -> deleteCounter.inc()) - .onError(res::send) - .forSingle(res::send); - } - - /** - * Handler for {@code GET /todo/id}. - * - * @param req the server request - * @param res the server response - */ - private void get(ServerRequest req, ServerResponse res) { - bsc.get(req.path().param("id")) - .onError(res::send) - .forSingle(res::send); - } -} diff --git a/examples/todo-app/frontend/src/main/java/io/helidon/demo/todos/frontend/package-info.java b/examples/todo-app/frontend/src/main/java/io/helidon/demo/todos/frontend/package-info.java deleted file mode 100644 index 94289600..00000000 --- a/examples/todo-app/frontend/src/main/java/io/helidon/demo/todos/frontend/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2018, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * TODOs Demo application frontend. - */ -package io.helidon.demo.todos.frontend; diff --git a/examples/todo-app/frontend/src/main/resources/WEB/css/styles.css b/examples/todo-app/frontend/src/main/resources/WEB/css/styles.css deleted file mode 100644 index 7c36bb76..00000000 --- a/examples/todo-app/frontend/src/main/resources/WEB/css/styles.css +++ /dev/null @@ -1,427 +0,0 @@ -/* - * Copyright (c) 2023, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -html, -body { - margin: 0 auto; - padding: 0; - font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; - line-height: 1.4em; - background: #f5f5f5; - color: #4d4d4d; - min-width: 600px; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - font-weight: 300; -} - -input { - font-family: inherit; - font-weight: inherit; - color: inherit; - border: 0; -} - -button { - margin: 0; - padding: 0; - border: 0; - background: none; - font-size: 100%; - vertical-align: baseline; - font-family: inherit; - font-weight: inherit; - color: inherit; - cursor: pointer; - -webkit-appearance: none; - appearance: none; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -:focus { - outline: 0; -} - -.hidden { - display: none; -} - -nav { - display: flex; - align-items: center; - padding: 0 20px 0 20px; - height: 80px; -} - -.spacer { - flex-grow: 1; -} - -.row { - display: flex; - flex-direction: row; - align-items: center; -} - -.column { - display: flex; - flex-direction: column; - align-items: center; -} - -.wrap { - display: none; -} - -.todoapp { - width: 550px; - background: #fff; - margin: 130px 0 40px 0; - position: relative; - box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), - 0 25px 50px 0 rgba(0, 0, 0, 0.1); -} - -.todoapp input::-webkit-input-placeholder { - font-style: italic; - font-weight: 300; - color: #e6e6e6; -} - -.todoapp input::-moz-placeholder { - font-style: italic; - font-weight: 300; - color: #e6e6e6; -} - -.todoapp h1 { - position: absolute; - top: -155px; - width: 100%; - font-size: 100px; - font-weight: 100; - text-align: center; - color: rgba(175, 47, 47, 0.15); - text-rendering: optimizeLegibility; -} - -.new-todo { - position: relative; - margin: 0; - width: 100%; - font-size: 24px; - font-family: inherit; - font-weight: inherit; - line-height: 1.4em; - color: inherit; - padding: 6px; - border: 1px solid #999; - box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); - box-sizing: border-box; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.new-todo { - padding: 16px 16px 16px 60px; - border: none; - background: rgba(0, 0, 0, 0.003); - box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03); -} - -.main { - position: relative; - z-index: 2; - border-top: 1px solid #e6e6e6; -} - -.checkbox { - border: none; /* Mobile Safari */ - appearance: none; - opacity: 0.25; - -webkit-appearance: none; - margin: 10px; -} - -.checkbox:after { - font-family: 'Material Symbols Outlined'; - font-size: 25px; - transition: opacity 0.2s ease-out; - cursor: pointer; -} - -.checkbox:hover { - opacity: 0.5; -} - -.checkbox:active { - opacity: 0.75; -} - -.toggle-all:after { - content: "\e877"; -} - -.todo-list { - margin: 0; - padding: 0; - list-style: none; -} - -.todo-list li { - position: relative; - font-size: 24px; - border-bottom: 1px solid #ededed; -} - -.todo-list li:last-child { - border-bottom: none; -} - -.todo-list .view, .todo-list .edit { - flex-grow: 1; - padding: 15px 15px 15px 60px; - line-height: 1.2; - font-size: 24px; -} - -.todo-list li.editing { - padding: 0; -} - -.todo-list li.editing .edit { - display: block; -} - -.todo-list li.editing .view { - display: none; -} - -.todo-list li .toggle { - margin: 10px; - display: flex; - align-items: center; -} - -.toggle:after { - content: "\ef4a"; -} - -.toggle:checked:after { - content: "\e86c"; -} - -.todo-list li label { - word-break: break-all; - padding: 15px 15px 15px 60px; - display: block; - line-height: 1.2; - transition: color 0.4s; -} - -.todo-list li.completed label { - color: #d9d9d9; - text-decoration: line-through; -} - -.todo-list li .actions { - display: flex; - align-items: center; - margin: 10px; -} - -.todo-list li .actions button { - margin: 5px; - opacity: 0.25; - font-size: 0; - cursor: pointer; - transition: opacity 0.2s ease-out; -} - -.todo-list li .actions button:hover { - opacity: 0.5; -} - -.todo-list li .actions button:active { - opacity: 0.75; -} - -.todo-list li .actions button:before { - font-family: 'Material Symbols Outlined'; - font-size: 25px; -} - -.todo-list li .actions .update:before { - content: '\e3c9' -} - -.todo-list li.editing .actions .update:before { - content: '\e5ca' !important; -} - -.todo-list li .actions .delete:before { - content: '\e872' -} - -.todo-list li .edit { - display: none; -} - -.todo-list li.editing:last-child { - margin-bottom: -1px; -} - -.footer { - color: #777; - padding: 10px 15px; - height: 20px; - text-align: center; - border-top: 1px solid #e6e6e6; -} - -.footer:before { - content: ''; - position: absolute; - right: 0; - bottom: 0; - left: 0; - height: 50px; - overflow: hidden; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), - 0 8px 0 -3px #f6f6f6, - 0 9px 1px -3px rgba(0, 0, 0, 0.2), - 0 16px 0 -6px #f6f6f6, - 0 17px 2px -6px rgba(0, 0, 0, 0.2); -} - -.todo-count { - float: left; - text-align: left; -} - -.todo-count strong { - font-weight: 300; -} - -.filters { - margin: 0; - padding: 0; - list-style: none; - position: absolute; - right: 0; - left: 0; -} - -.filters li { - display: inline; -} - -.filters li a { - color: inherit; - margin: 3px; - padding: 3px 7px; - text-decoration: none; - border: 1px solid transparent; - border-radius: 3px; -} - -.filters li a:hover { - border-color: rgba(175, 47, 47, 0.1); -} - -.filters li a.selected { - border-color: rgba(175, 47, 47, 0.2); -} - -.clear-completed, html .clear-completed:active { - float: right; - position: relative; - line-height: 20px; - text-decoration: none; - cursor: pointer; -} - -.clear-completed:hover { - text-decoration: underline; -} - -.info { - margin: 0; - color: #bfbfbf; - font-size: 14px; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - text-align: center; -} - -.info p { - line-height: 1; -} - -.info a { - color: inherit; - text-decoration: none; - font-weight: 400; -} - -.info a:hover { - text-decoration: underline; -} - -@media screen and (-webkit-min-device-pixel-ratio: 0) { - .toggle-all, .todo-list li .toggle { - background: none; - } - - .todo-list li .toggle { - height: 40px; - } -} - -@media (max-width: 430px) { - .footer { - height: 50px; - } - - .filters { - bottom: 10px; - } -} - -#user-info { - display:none; -} - -#user-info > div { - display:flex; -} - -#user-info .sign-out { - margin-right: 10px; - background: white; - padding: 5px; - border: 1px solid #dadce0; - border-radius: 4px; - transition: background-color .218s, border-color .218s; -} - -#user-info .sign-out:hover { - border-color: #d2e3fc; - background-color: rgba(66,133,244,.04); -} -#user-info .sign-out:active { - background-color: rgba(66,133,244,.1); -} diff --git a/examples/todo-app/frontend/src/main/resources/WEB/index.html b/examples/todo-app/frontend/src/main/resources/WEB/index.html deleted file mode 100644 index 6e45e004..00000000 --- a/examples/todo-app/frontend/src/main/resources/WEB/index.html +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - Helidon TodoMVC - - - - -

-
-
-
-
-

todos

-
- - -
-
-
-
    -
    -
    -
    -
    -
    -

    Helidon implementation of TodoMVC

    -
    -
    - - - - - - - - - diff --git a/examples/todo-app/frontend/src/main/resources/WEB/js/app.js b/examples/todo-app/frontend/src/main/resources/WEB/js/app.js deleted file mode 100644 index 560a075b..00000000 --- a/examples/todo-app/frontend/src/main/resources/WEB/js/app.js +++ /dev/null @@ -1,465 +0,0 @@ -/* - * Copyright (c) 2018, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* global jQuery, Router */ - -/** - * @typedef {Object} Handlebars - * @property {function(string, function(*, *, HandlebarsOpts))} registerHelper - * @property {function(HTMLElement):function(object):string} compile - */ - -/** - * @typedef {Object} HandlebarsOpts - * @property {function(object)} fn - * @property {function(object)} inverse - */ - -/** - * @typedef {Object} Google - * @property {GoogleAccount} accounts - */ - -/** - * @typedef {Object} GoogleAccount - * @property {GoogleId} id - */ - -/** - * @typedef {Object} GoogleId - * @property {function({})} initialize - * @property {function()} prompt - * @property {function(Element, Object)} renderButton - * @property {function(string} revoke - */ - -/** - * @typedef {Object} Todo - * @property {string} title - * @property {string=} id - * @property {boolean} completed - */ - -class TodoClient { - - constructor(access_token) { - this.access_token = access_token - } - - /** - * List all entries. - * @return {Promise} - */ - list() { - return this._ajax('GET', '/api/todo'); - } - - /** - * Create a new entry. - * @property {Todo} data - * @return {Promise} - */ - create(item) { - return this._ajax('POST', '/api/todo', item); - } - - /** - * Toggle an entry. - * @param {Todo} item - * @param {boolean} completed - */ - toggle(item, completed) { - const data = {...item, completed}; - return this._ajax('PUT', `/api/todo/${item.id}`, data); - } - - /** - * Update an entry. - * @param {Todo} item - * @return {Promise} - */ - update(item) { - return this._ajax('PUT', `/api/todo/${item.id}`, item); - } - - /** - * Delete an entry. - * @param {string} id - * @return {Promise} - */ - delete(id) { - return this._ajax('DELETE', `/api/todo/${id}`); - } - - /** - * Batch requests. - * @param {Todo[]} items - * @param {function(Todo):boolean} filter - * @param {function(Todo, number): Promise} fn - * @return {Promise[]>} - */ - batch(items, filter, fn) { - const promises = []; - items.forEach((e, i) => { - if (filter(e)) { - promises.push(fn(e, i)); - } - }) - return Promise.all(promises); - } - - /** - * Toggle all items. - * @property {Todo[]} items - * @property {boolean} completed - * @return {Promise} - */ - toggleAll(items, completed) { - const result = [...items]; - return this.batch(items, e => e.completed !== completed, (e, i) => { - return this.toggle(e, completed).then(data => { - result[i] = data; - }) - }).then(() => result); - } - - /** - * Delete all completed items. - * @param {Todo[]} items - * @return {Promise} - */ - deleteCompleted(items) { - const indexes = []; - const result = [...items]; - return this.batch(items, e => e.completed, (data, index) => { - indexes.push(index); - return this._ajax('DELETE', `/api/todo/${data.id}`); - }).then(() => { - indexes.sort(); - for (let i = indexes.length - 1; i >= 0; i--) { - result.splice(indexes[i], 1); - } - return result; - }) - } - - /** - * @param {string} type - * @param {string} path - * @param {object=} data - * @return {Promise} - */ - _ajax(type, path, data) { - while (path.startsWith('/')) { - path = path.substring(1); - } - return new Promise((resolve, reject) => { - // noinspection JSUnusedGlobalSymbols - $.ajax({ - type: type, - beforeSend: (request) => { - if (this.access_token) { - request.setRequestHeader('Authorization', `Bearer ${this.access_token}`); - } - }, - url: window.location.pathname + path, - dataType: 'json', - contentType: 'application/json;charset=utf-8', - data: data && JSON.stringify(data) - }).done((resData) => { - resolve(resData); - }).fail((data, textStatus) => { - reject(textStatus); - }); - }) - } -} - -class App { - - constructor() { - this.client = null; - this.token = null; - this.todos = []; - this.todoTemplate = Handlebars.compile($('#todo-template').html()); - this.footerTemplate = Handlebars.compile($('#footer-template').html()); - this.router = new Router({ - '/:filter': (filter) => { - this.filter = filter; - this.render(); - } - }); - $('.sign-out').on('click', e => this.signOut()); - $('.new-todo').on('keyup', e => this.create(e)); - $('.toggle-all').on('change', e => this.toggleAll(e)); - $('.footer').on('click', '.clear-completed', () => this.deleteCompleted()); - $('.todo-list') - .on('change', '.toggle', e => this.toggle(e)) - .on('click', '.update', e => this.editingMode(e)) - .on('focusout', '.edit', e => this.update(e)) - .on('click', '.delete', e => this.destroy(e)); - } - - signOut() { - const google = /** @type {Google} */ (window['google']); - google.accounts.id.revoke(this.token.sub); - this.client = null; - this.token = null; - $('#user-info').fadeOut(); - $('.wrap').fadeOut(); - google.accounts.id.prompt(); - } - - /** - * Init the application. - */ - init(access_token) { - this.client = new TodoClient(access_token); - this.token = JSON.parse(atob(access_token.split(".")[1])); - const signedIn = access_token && true || false; - if (signedIn) { - this.init0().then(() => { - $('.wrap').fadeIn(); - $('#user-info').fadeIn(); - }) - } - } - - init0() { - return this.client.list().then(items => { - this.todos = items; - this.router.init('/all'); - }).catch(console.error); - } - - /** - * Render. - */ - render() { - const todos = this.getFilteredTodos(); - $('.todo-list').html(this.todoTemplate(todos.map((e, i) => { - e.index = i; - return e; - }))); - $('.main').toggle(todos.length > 0); - $('.toggle-all').prop('checked', this.getActiveTodos().length === 0); - this.renderFooter(); - $('.new-todo').focus(); - } - - /** - * Render the footer. - */ - renderFooter() { - const todoCount = this.todos.length; - const activeTodoCount = this.getActiveTodos().length; - const template = this.footerTemplate({ - activeTodoCount: activeTodoCount, - activeTodoWord: this.pluralize(activeTodoCount, 'item'), - completedTodos: todoCount - activeTodoCount, - filter: this.filter - }); - $('.footer').toggle(todoCount > 0).html(template); - } - - /** - * Pluralize the given word. - * @param {number} count - * @param {string} word - * @return {string} - */ - pluralize(count, word) { - return count === 1 ? word : word + 's'; - } - - /** - * Get the active entries. - * @return {Todo[]} - */ - getActiveTodos() { - return this.todos.filter((todo) => { - return !todo.completed; - }); - } - - /** - * Get the completed entries. - * @return {Todo[]} - */ - getCompletedTodos() { - return this.todos.filter((todo) => todo.completed); - } - - /** - * Get the entries for the current filter. - * @return {Todo[]} - */ - getFilteredTodos() { - if (this.filter === 'active') { - return this.getActiveTodos(); - } - if (this.filter === 'completed') { - return this.getCompletedTodos(); - } - return this.todos; - } - - /** - * Toggle all entries. - * @param {Event} e - */ - toggleAll(e) { - const isChecked = $(e.target).prop('checked'); - this.client.toggleAll(this.todos, isChecked).then(items => { - this.todos = items; - this.render(); - }).catch(console.error); - } - - /** - * Delete all completed entries. - */ - deleteCompleted() { - this.client.deleteCompleted(this.todos).then(items => { - this.todos = items; - this.render(); - }).catch(console.error); - } - - /** - * Create a new entry. - * @param {KeyboardEvent} e - */ - create(e) { - const input = $(e.target); - const val = input.val().trim(); - if (e.key !== "Enter" || !val) { - return; - } - input.val(''); - const item = {title: val, completed: false}; - this.client.create(item).then((data) => { - this.todos.push(data); - this.render(); - }).catch(console.error) - } - - /** - * Toggle an entry. - * @param {Event} e - * @return {Promise} - */ - toggle(e) { - const info = this.entryInfo(e.target); - const entry = this.todos[info.index]; - return this.client.toggle(entry, !entry.completed).then((data) => { - this.todos[info.index] = data; - this.render(); - }).catch(console.error); - } - - /** - * Update an entry. - * @param {{target: Element}} e - * @return {Promise} - */ - update(e) { - const input = $(e.target); - const val = input.val().trim(); - if (!val) { - this.destroy(e); - return Promise.resolve(); - } else { - const info = this.entryInfo(e.target); - const newData = {...this.todos[info.index], title: val}; - return this.client.update(newData).then(data => { - this.todos[info.index] = data; - this.render(); - }).catch(console.error); - } - } - - /** - * Destroy an entry. - * @param {{target: Element}} e - */ - destroy(e) { - const info = this.entryInfo(e.target); - this.client.delete(info.id).then(() => { - this.todos.splice(info.index, 1); - this.render(); - }).catch(console.error); - } - - /** - * Edit an entry. - * @param {Event} e - */ - editingMode(e) { - const listElt = $(e.target).closest('li'); - const editing = listElt.hasClass('editing'); - if (editing) { - const input = listElt.find('.edit'); - this.update({ - target: input[0] - }).then(() => listElt.removeClass('editing')); - } else { - const input = listElt.addClass('editing').find('.edit'); - // puts caret at end of input - const tmpStr = input.val(); - input.val(''); - input.val(tmpStr); - input.focus(); - } - } - - /** - * Get the entry info for an element. - * @param {Element} el - * @return {{index: number, id: string}} - */ - entryInfo(el) { - const id = $(el).closest('li').data('id'); - const todos = this.todos; - let i = todos.length; - while (i--) { - if (todos[i].id === id) { - return {id, index: i}; - } - } - } -} - -window.onload = () => { - const google = /** @type {Google} */ (window['google']); - const Handlebars = /** @type {Handlebars} */ (window['Handlebars']); - - Handlebars.registerHelper('eq', (a, b, options) => { - return a === b ? options.fn(this) : options.inverse(this); - }); - - const app = new App(); - - // noinspection JSUnusedGlobalSymbols,SpellCheckingInspection - google.accounts.id.initialize({ - client_id: '1048216952820-6a6ke9vrbjlhngbc0al0dkj9qs9tqbk2.apps.googleusercontent.com', - auto_select: true, - callback: response => { - app.init(response.credential) - } - }); - google.accounts.id.prompt(); -} diff --git a/examples/todo-app/frontend/src/main/resources/application.yaml b/examples/todo-app/frontend/src/main/resources/application.yaml deleted file mode 100644 index 2544d24d..00000000 --- a/examples/todo-app/frontend/src/main/resources/application.yaml +++ /dev/null @@ -1,60 +0,0 @@ -# -# Copyright (c) 2018, 2024 Oracle and/or its affiliates. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -env: docker - -webserver: - port: 8080 - -cors: - allow-origins: ["https://cdnjs.cloudflare.com", "https://raw.githubusercontent.com"] - allow-methods: ["GET"] - -tracing: - service: "todo:front" - port: 9411 - -services: - backend.endpoint: "http://localhost:8854" - -security: - config: - require-encryption: false - aes.insecure-passphrase: "changeit" - provider-policy: - type: "COMPOSITE" - authentication: - - name: "google-login" - outbound: - - name: "google-login" - - name: "http-signatures" - providers: - - google-login: - client-id: "1048216952820-6a6ke9vrbjlhngbc0al0dkj9qs9tqbk2.apps.googleusercontent.com" - outbound: - - name: "backend" - hosts: [ "localhost" ] - - abac: - - http-signatures: - outbound: - - name: "backend" - hosts: [ "localhost" ] - signature: - key-id: "frontend" - hmac.secret: "${CLEAR=changeit}" - web-server: - paths: - - path: "/api/{+}" - authenticate: true diff --git a/examples/todo-app/frontend/src/main/resources/logging.properties b/examples/todo-app/frontend/src/main/resources/logging.properties deleted file mode 100644 index 3ed94f67..00000000 --- a/examples/todo-app/frontend/src/main/resources/logging.properties +++ /dev/null @@ -1,28 +0,0 @@ -# -# Copyright (c) 2017, 2024 Oracle and/or its affiliates. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -#All attributes details -handlers=io.helidon.common.HelidonConsoleHandler -java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n - -#All log level details -.level=WARNING -io.helidon.webserver.level=INFO -io.helidon.security.level=INFO -io.helidon.tracing.level=FINE -AUDIT.level=FINEST - - diff --git a/examples/todo-app/frontend/src/test/java/io/helidon/demo/todos/frontend/TodoServiceTest.java b/examples/todo-app/frontend/src/test/java/io/helidon/demo/todos/frontend/TodoServiceTest.java deleted file mode 100644 index 22b88f6c..00000000 --- a/examples/todo-app/frontend/src/test/java/io/helidon/demo/todos/frontend/TodoServiceTest.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (c) 2021, 2024 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.demo.todos.frontend; - -import java.time.Duration; -import java.util.Base64; -import java.util.List; -import java.util.Map; - -import io.helidon.common.http.Http; -import io.helidon.config.Config; -import io.helidon.config.ConfigSources; -import io.helidon.media.jsonp.JsonpSupport; -import io.helidon.security.Security; -import io.helidon.security.integration.webserver.WebSecurity; -import io.helidon.webclient.WebClient; -import io.helidon.webclient.WebClientResponse; -import io.helidon.webclient.security.WebClientSecurity; -import io.helidon.webserver.Routing; -import io.helidon.webserver.ServerRequest; -import io.helidon.webserver.ServerResponse; -import io.helidon.webserver.Service; -import io.helidon.webserver.WebServer; - -import javax.json.Json; -import javax.json.JsonArray; -import javax.json.JsonObject; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import static io.helidon.config.ConfigSources.classpath; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -public class TodoServiceTest { - - private static final JsonObject TODO = Json.createObjectBuilder().add("msg", "todo").build(); - private static final JsonArray TODOS = Json.createArrayBuilder().add(TODO).build(); - private static final String ENCODED_ID = Base64.getEncoder().encodeToString("john:changeit".getBytes()); - - private static WebServer serverBackend; - private static WebServer serverFrontend; - private static WebClient client; - - private static class BackendServiceMock implements Service { - - @Override - public void update(Routing.Rules rules) { - rules.get("/", this::list) - .get("/{id}", this::get) - .delete("/{id}", this::get) - .post(this::echo) - .put("/{id}", this::echo); - } - - void list(ServerRequest req, ServerResponse res) { - res.send(TODOS); - } - - void get(ServerRequest req, ServerResponse res) { - res.send(TODO); - } - - void echo(ServerRequest req, ServerResponse res) { - req.content().as(JsonObject.class) - .onError(res::send) - .forSingle(res::send); - } - } - - @BeforeAll - public static void init() { - serverBackend = WebServer.builder() - .routing(Routing.builder() - .register("/api/backend", new BackendServiceMock())) - .addMediaSupport(JsonpSupport.create()) - .build() - .start() - .await(Duration.ofMinutes(1)); - - Config config = Config.builder() - .sources(List.of( - classpath("application-test.yaml"), - ConfigSources.create(Map.of("services.backend.endpoint", "http://127.0.0.1:" + serverBackend.port())))) - .build(); - - Security security = Security.create(config.get("security")); - - serverFrontend = WebServer.builder() - .routing(Routing.builder() - .register(WebSecurity.create(security, config.get("security"))) - .register("/api", new TodoService(new BackendServiceClient(config)))) - .config(config.get("webserver")) - .addMediaSupport(JsonpSupport.create()) - .build() - .start() - .await(Duration.ofMinutes(1)); - - client = WebClient.builder() - .baseUri("http://localhost:" + serverFrontend.port()) - .addMediaSupport(JsonpSupport.create()) - .useSystemServiceLoader(false) - .addService(WebClientSecurity.create(security)) - .addHeader(Http.Header.AUTHORIZATION, "Basic " + ENCODED_ID) - .build(); - } - - @AfterAll - public static void stopServers() { - if (serverBackend != null) { - serverBackend.shutdown(); - } - if (serverFrontend != null) { - serverFrontend.shutdown(); - } - } - - @Test - public void testList() { - WebClientResponse response = client.get() - .path("/api/todo") - .request() - .await(); - assertThat(response.status(), is(Http.Status.OK_200)); - JsonArray jsonValues = response.content().as(JsonArray.class).await(); - assertThat(jsonValues.getJsonObject(0), is(TODO)); - } - - @Test - public void testCreate() { - WebClientResponse response = client.post() - .path("/api/todo") - .submit(TODO) - .await(); - assertThat(response.status(), is(Http.Status.CREATED_201)); - JsonObject jsonObject = response.content().as(JsonObject.class).await(); - assertThat(jsonObject, is(TODO)); - } - - @Test - public void testGet() { - WebClientResponse response = client.get() - .path("/api/todo/1") - .request() - .await(); - - assertThat(response.status(), is(Http.Status.OK_200)); - JsonObject jsonObject = response.content().as(JsonObject.class).await(); - assertThat(jsonObject, is(TODO)); - } - - @Test - public void testDelete() { - WebClientResponse response = client.delete() - .path("/api/todo/1") - .request() - .await(); - - assertThat(response.status(), is(Http.Status.OK_200)); - JsonObject jsonObject = response.content().as(JsonObject.class).await(); - assertThat(jsonObject, is(TODO)); - } - - @Test - public void testUpdate() { - WebClientResponse response = client.put() - .path("/api/todo/1") - .submit(TODO) - .await(); - - assertThat(response.status(), is(Http.Status.OK_200)); - JsonObject jsonObject = response.content().as(JsonObject.class).await(); - assertThat(jsonObject, is(TODO)); - } -} diff --git a/examples/todo-app/frontend/src/test/resources/application-test.yaml b/examples/todo-app/frontend/src/test/resources/application-test.yaml deleted file mode 100644 index 37993bfc..00000000 --- a/examples/todo-app/frontend/src/test/resources/application-test.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# -# Copyright (c) 2021, 2024 Oracle and/or its affiliates. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -webserver: - port: 0 - -security: - providers: - - http-basic-auth: - realm: "helidon" - users: - - login: "john" - password: "changeit" - web-server: - paths: - - path: "/api/{+}" - authenticate: true diff --git a/examples/todo-app/pom.xml b/examples/todo-app/pom.xml deleted file mode 100644 index b487aeeb..00000000 --- a/examples/todo-app/pom.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - 4.0.0 - - io.helidon.examples - helidon-examples-project - 1.0.0-SNAPSHOT - - - io.helidon.examples.todos - helidon-examples-todo-app-project - pom - Helidon Examples TODO Demo - - - TODO demo application - - - - frontend - backend - -