diff --git a/application/pom.xml b/application/pom.xml index 81e0b9ed6545187d17d027b84c551ca3e8edc712..3eed9228a842f06d9b2fc50ff2723f86873ffb51 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -12,10 +12,6 @@ 1.0.9 - - com.fasterxml.jackson.dataformat - jackson-dataformat-csv - com.openhtmltopdf diff --git a/pom.xml b/pom.xml index 5240e0bfc950ace5cd0469f4b1aa19f5b7bf4b54..3e9de2e9c6c0ebf665450f50be5bfd4eb09dd99f 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ 1.4.10-SNAPSHOT pom - 0.6.6 + 0.6.7 2.22.1 3.8.1 11 @@ -84,6 +84,10 @@ io.quarkus quarkus-resteasy-jackson + + com.fasterxml.jackson.dataformat + jackson-dataformat-csv + com.fasterxml.jackson.datatype diff --git a/portal/src/main/java/org/eclipsefoundation/eclipsedb/dto/EventLogSessions.java b/portal/src/main/java/org/eclipsefoundation/eclipsedb/dto/EventLogSessions.java new file mode 100644 index 0000000000000000000000000000000000000000..8e2c23eccdf633d0930fde19320b169a6ef7ff10 --- /dev/null +++ b/portal/src/main/java/org/eclipsefoundation/eclipsedb/dto/EventLogSessions.java @@ -0,0 +1,163 @@ +/** + * Copyright (c) 2022 Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * Author: Martin Lowe + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipsefoundation.eclipsedb.dto; + +import java.sql.Date; +import java.time.LocalDateTime; +import java.util.Calendar; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.ws.rs.core.MultivaluedMap; + +import org.eclipsefoundation.eclipsedb.namespaces.EclipseDBParameterNames; +import org.eclipsefoundation.persistence.dto.BareNode; +import org.eclipsefoundation.persistence.dto.filter.DtoFilter; +import org.eclipsefoundation.persistence.model.DtoTable; +import org.eclipsefoundation.persistence.model.ParameterizedCallStatement; +import org.eclipsefoundation.persistence.model.ParameterizedSQLStatement; +import org.eclipsefoundation.persistence.model.ParameterizedSQLStatementBuilder; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +@Entity +public class EventLogSessions extends BareNode { + public static final DtoTable TABLE = new DtoTable(EventLogSessions.class, "els"); + + @Id + @Column(name = "pk1") + private int organizationId; + private int uniqueHits; + private int totalHits; + private LocalDateTime earliestHit; + private LocalDateTime latestHit; + + @JsonIgnore + @Override + public Object getId() { + return getOrganizationId(); + } + + /** + * @return the organizationId + */ + public int getOrganizationId() { + return organizationId; + } + + /** + * @param organizationId the organizationId to set + */ + public void setOrganizationId(int organizationId) { + this.organizationId = organizationId; + } + + /** + * @return the uniqueHits + */ + public int getUniqueHits() { + return uniqueHits; + } + + /** + * @param uniqueHits the uniqueHits to set + */ + public void setUniqueHits(int uniqueHits) { + this.uniqueHits = uniqueHits; + } + + /** + * @return the totalHits + */ + public int getTotalHits() { + return totalHits; + } + + /** + * @param totalHits the totalHits to set + */ + public void setTotalHits(int totalHits) { + this.totalHits = totalHits; + } + + /** + * @return the earliestHit + */ + public LocalDateTime getEarliestHit() { + return earliestHit; + } + + /** + * @param earliestHit the earliestHit to set + */ + public void setEarliestHit(LocalDateTime earliestHit) { + this.earliestHit = earliestHit; + } + + /** + * @return the latestHit + */ + public LocalDateTime getLatestHit() { + return latestHit; + } + + /** + * @param latestHit the latestHit to set + */ + public void setLatestHit(LocalDateTime latestHit) { + this.latestHit = latestHit; + } + + @Singleton + public static class SysEventLogFilter implements DtoFilter { + @Inject + ParameterizedSQLStatementBuilder builder; + + @Override + public ParameterizedSQLStatement getFilters(MultivaluedMap params, boolean isRoot) { + ParameterizedCallStatement stmt = builder.buildCallStatement(TABLE); + stmt.setCallStatement("sp_evtlog_sessions"); + + // since check + String since = params.getFirst(EclipseDBParameterNames.SINCE.getName()); + if (since != null) { + stmt.addClause(new ParameterizedSQLStatement.Clause("", new Object[] { since })); + } else { + // default to 1 year in the past + Calendar c = Calendar.getInstance(); + c.set(Calendar.YEAR, c.get(Calendar.YEAR) - 1); + stmt + .addClause(new ParameterizedSQLStatement.Clause("", + new Object[] { new Date(c.toInstant().getNano()) })); + } + + // servername check + String serverName = params.getFirst(EclipseDBParameterNames.SERVER_NAME.getName()); + if (serverName != null) { + stmt.addClause(new ParameterizedSQLStatement.Clause("", new Object[] { serverName })); + } else { + stmt.addClause(new ParameterizedSQLStatement.Clause("", new Object[] { "" })); + } + + return stmt; + } + + @Override + public Class getType() { + return EventLogSessions.class; + } + } + +} diff --git a/portal/src/main/java/org/eclipsefoundation/eclipsedb/namespaces/EclipseDBParameterNames.java b/portal/src/main/java/org/eclipsefoundation/eclipsedb/namespaces/EclipseDBParameterNames.java index 0a5b60473691eac3120be9ebe02da25046721adc..5a7adebb9bb2249fc89dba84bd5f64cda6f7fc0e 100644 --- a/portal/src/main/java/org/eclipsefoundation/eclipsedb/namespaces/EclipseDBParameterNames.java +++ b/portal/src/main/java/org/eclipsefoundation/eclipsedb/namespaces/EclipseDBParameterNames.java @@ -15,9 +15,12 @@ public class EclipseDBParameterNames implements UrlParameterNamespace { public static final UrlParameter PRODUCT_ID = new UrlParameter("product_id"); public static final UrlParameter ORGANIZATION_ID = new UrlParameter("organization_id"); public static final UrlParameter USERNAME = new UrlParameter("username"); + public static final UrlParameter SERVER_NAME = new UrlParameter("server_name"); + public static final UrlParameter SINCE = new UrlParameter("since"); + public static final UrlParameter LOG_TABLE = new UrlParameter("log_table"); private static final List params = Collections - .unmodifiableList(Arrays.asList(PRODUCT_ID, ORGANIZATION_ID, USERNAME)); + .unmodifiableList(Arrays.asList(PRODUCT_ID, ORGANIZATION_ID, USERNAME, SERVER_NAME, SINCE, LOG_TABLE)); @Override public List getParameters() { diff --git a/portal/src/main/java/org/eclipsefoundation/membership/portal/model/reports/MemberAdoptionReportItem.java b/portal/src/main/java/org/eclipsefoundation/membership/portal/model/reports/MemberAdoptionReportItem.java new file mode 100644 index 0000000000000000000000000000000000000000..92bff23cbd2007c158f246df8ef2db373f9238e0 --- /dev/null +++ b/portal/src/main/java/org/eclipsefoundation/membership/portal/model/reports/MemberAdoptionReportItem.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2022 Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * Author: Martin Lowe + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipsefoundation.membership.portal.model.reports; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + +@AutoValue +@JsonDeserialize(builder = AutoValue_MemberAdoptionReportItem.Builder.class) +public abstract class MemberAdoptionReportItem { + + public abstract int getOrganizationId(); + public abstract String getOrganizationName(); + public abstract LocalDateTime getFirstHit(); + public abstract int getTotalHits(); + public abstract int getUniqueHits(); + + public static Builder builder() { + return new AutoValue_MemberAdoptionReportItem.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setOrganizationId(int orgId); + public abstract Builder setOrganizationName(String orgName); + public abstract Builder setFirstHit(LocalDateTime firstHit); + public abstract Builder setTotalHits(int totalHits); + public abstract Builder setUniqueHits(int uniqueHits); + public abstract MemberAdoptionReportItem build(); + } +} diff --git a/portal/src/main/java/org/eclipsefoundation/membership/portal/resources/AbstractRESTResource.java b/portal/src/main/java/org/eclipsefoundation/membership/portal/resources/AbstractRESTResource.java index a2c8cca90e329d2af4738dea449d6c6069021b65..f193bd1b384a29e95e654971557c1c7f11a07d54 100644 --- a/portal/src/main/java/org/eclipsefoundation/membership/portal/resources/AbstractRESTResource.java +++ b/portal/src/main/java/org/eclipsefoundation/membership/portal/resources/AbstractRESTResource.java @@ -65,8 +65,6 @@ public abstract class AbstractRESTResource { @Inject RequestWrapper wrap; - @Inject - ResponseHelper responseBuider; @Inject CSRFHelper csrfHelper; diff --git a/portal/src/main/java/org/eclipsefoundation/membership/portal/resources/ReportsResource.java b/portal/src/main/java/org/eclipsefoundation/membership/portal/resources/ReportsResource.java new file mode 100644 index 0000000000000000000000000000000000000000..627daae5ae5038809b2c639a65e6475abec5ce6e --- /dev/null +++ b/portal/src/main/java/org/eclipsefoundation/membership/portal/resources/ReportsResource.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2022 Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * Author: Martin Lowe + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipsefoundation.membership.portal.resources; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; +import java.util.stream.Collectors; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipsefoundation.eclipsedb.dto.EventLogSessions; +import org.eclipsefoundation.eclipsedb.namespaces.EclipseDBParameterNames; +import org.eclipsefoundation.membership.portal.model.reports.MemberAdoptionReportItem; +import org.eclipsefoundation.persistence.dao.impl.DefaultHibernateDao; +import org.eclipsefoundation.persistence.model.RDBMSQuery; +import org.jboss.resteasy.specimpl.MultivaluedMapImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.SequenceWriter; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.csv.CsvMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import io.quarkus.security.Authenticated; + +@Path("reports") +@Authenticated +public class ReportsResource extends AbstractRESTResource { + public static final Logger LOGGER = LoggerFactory.getLogger(ReportsResource.class); + + @ConfigProperty(name = "eclipse.security.reports.allowed-users") + List allowedUsers; + + @Inject + DefaultHibernateDao dao; + + private static final CsvMapper CSV_MAPPER = (CsvMapper) new CsvMapper() + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + + @GET + @Path("adoption_rate") + @Produces(MediaType.TEXT_PLAIN) + public Response ok() throws IOException { + // check that the user is in the allowed user list + if (!allowedUsers.contains(ident.getPrincipal().getName())) { + return Response.status(403).build(); + } + // set filters for db query, matching on events for current host + MultivaluedMap params = new MultivaluedMapImpl<>(); + params.add(EclipseDBParameterNames.SERVER_NAME.getName(), request.getServerName()); + RDBMSQuery q = new RDBMSQuery<>(wrap, filters.get(EventLogSessions.class), params); + + try (StringWriter sw = new StringWriter()) { + // create the CSV writer for membership forms + SequenceWriter writer = CSV_MAPPER + .writer(CSV_MAPPER.schemaFor(MemberAdoptionReportItem.class).withHeader()) + .writeValues(sw); + // retrieve the membership form for the current state and write to output + writer.writeAll(adapt(dao.get(q))); + return Response.ok(sw.toString()).build(); + } + } + + private List adapt(List origin) { + return origin + .stream() + .map(o -> MemberAdoptionReportItem + .builder() + .setOrganizationId(o.getOrganizationId()) + .setFirstHit(o.getEarliestHit()) + .setTotalHits(o.getTotalHits()) + .setUniqueHits(o.getUniqueHits()) + .setOrganizationName( + orgService.getByID(Integer.toString(o.getOrganizationId())).get().getName()) + .build()) + .collect(Collectors.toList()); + } + +} diff --git a/portal/src/main/resources/application.properties b/portal/src/main/resources/application.properties index 379f529b62ec197732f1a82273fcb8aa33064a4b..bc235c6c6c8123c07141ab17672fa9cfed66abdd 100644 --- a/portal/src/main/resources/application.properties +++ b/portal/src/main/resources/application.properties @@ -75,6 +75,7 @@ quarkus.resteasy.path=/api eclipse.image-store.file-path=/app/organization/imagestore eclipse.image-store.web-root=${eclipse.app.base-url}/images/organization/ eclipse.api.application-group=eclipse +eclipse.security.reports.allowed-users=zfazli,webdev security.csrf.enabled=true security.csrf.enabled.distributed-mode=true eclipse.image-store.default-image-url=https://www.eclipse.org/eclipse.org-common/themes/solstice/public/images/logo/eclipse-foundation-white-orange.svg