updated public logs api

This commit is contained in:
Michele Artini 2023-12-14 11:10:43 +01:00
parent e4cbfe863b
commit a0e7425379
6 changed files with 184 additions and 244 deletions

View File

@ -1,35 +1,53 @@
package eu.dnetlib.organizations.controller;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import eu.dnetlib.organizations.model.view.ApiJournalView;
import eu.dnetlib.organizations.repository.OrganizationRepository;
import eu.dnetlib.organizations.repository.readonly.ApiJournalViewRepository;
import eu.dnetlib.organizations.utils.DatabaseUtils;
@RestController
@RequestMapping("/public-api")
public class PublicApiController {
@Autowired
private ApiJournalViewRepository apiJournalViewRepository;
private DatabaseUtils dbUtils;
@Autowired
private OrganizationRepository organizationRepository;
@GetMapping("/logs/{year}/{month}/{country}")
public void findJournalByCountry(@PathVariable final int year,
@PathVariable final int month,
@PathVariable final String country,
final HttpServletResponse res) throws IOException {
@GetMapping("/logs")
public ApiJournalView findJournalByDsId(@RequestParam final String id) {
return apiJournalViewRepository.findById(id).orElse(organizationRepository.findById(id).map(ApiJournalView::new).orElse(new ApiJournalView()));
if (month < 1 || month > 12) {
res.sendError(HttpStatus.BAD_REQUEST.value(), "Invalid month");
}
if (year < 2020 || year > 2100) {
res.sendError(HttpStatus.BAD_REQUEST.value(), "Invalid year");
}
@GetMapping("/logs/{country}/{page}/{size}")
public Page<ApiJournalView> findJournalByCountry(@PathVariable final String country, @PathVariable final int page, @PathVariable final int size) {
return apiJournalViewRepository.findByCountry(country, PageRequest.of(page, size));
res.setContentType(MediaType.TEXT_PLAIN.getType());
final ServletOutputStream out = res.getOutputStream();
dbUtils.obtainLogEntries(year, month, country).forEach(s -> {
try {
IOUtils.write(s, out, StandardCharsets.UTF_8);
} catch (final IOException e) {
throw new RuntimeException(e);
}
});
}
}

View File

@ -1,37 +0,0 @@
package eu.dnetlib.organizations.model.utils;
import java.io.Serializable;
import java.time.LocalDateTime;
public class ApiOperation implements Serializable {
private static final long serialVersionUID = -7111524441502889608L;
private String operation;
private String description;
private LocalDateTime date;
public String getOperation() {
return operation;
}
public void setOperation(final String operation) {
this.operation = operation;
}
public String getDescription() {
return description;
}
public void setDescription(final String description) {
this.description = description;
}
public LocalDateTime getDate() {
return date;
}
public void setDate(final LocalDateTime date) {
this.date = date;
}
}

View File

@ -1,87 +0,0 @@
package eu.dnetlib.organizations.model.view;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
import com.vladmihalcea.hibernate.type.json.JsonBinaryType;
import com.vladmihalcea.hibernate.type.json.JsonStringType;
import eu.dnetlib.organizations.model.Organization;
import eu.dnetlib.organizations.model.utils.ApiOperation;
@Entity
@Table(name = "api_journal_view")
@TypeDefs({
@TypeDef(name = "json", typeClass = JsonStringType.class),
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
})
public class ApiJournalView implements Serializable {
private static final long serialVersionUID = 1270660185726854334L;
@Id
@Column(name = "id")
private String id;
@Column(name = "name")
private String name;
@Column(name = "country")
private String country;
@Type(type = "json")
@Column(name = "logs", columnDefinition = "json")
private List<ApiOperation> logs;
public ApiJournalView() {}
public ApiJournalView(final Organization o) {
id = o.getId();
name = o.getName();
country = o.getCountry();
logs = new ArrayList<>();
}
public String getId() {
return id;
}
public void setId(final String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public String getCountry() {
return country;
}
public void setCountry(final String country) {
this.country = country;
}
public List<ApiOperation> getLogs() {
return logs;
}
public void setLogs(final List<ApiOperation> logs) {
this.logs = logs;
}
}

View File

@ -1,14 +0,0 @@
package eu.dnetlib.organizations.repository.readonly;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import eu.dnetlib.organizations.model.view.ApiJournalView;
@Repository
public interface ApiJournalViewRepository extends ReadOnlyRepository<ApiJournalView, String> {
Page<ApiJournalView> findByCountry(String country, Pageable page);
}

View File

@ -1,5 +1,6 @@
package eu.dnetlib.organizations.utils;
import java.io.StringWriter;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Arrays;
@ -104,12 +105,7 @@ public class DatabaseUtils {
private static final Log log = LogFactory.getLog(DatabaseUtils.class);
public enum VocabularyTable {
languages,
countries,
org_types,
id_types,
rel_types,
simrel_types
languages, countries, org_types, id_types, rel_types, simrel_types
}
@Transactional
@ -194,7 +190,7 @@ public class DatabaseUtils {
if (oldStatus == null) {
op = JournalOperations.NEW_SUGG_ORG;
message = "Created a new suggested org";
} else if (oldStatus != null) {
} else {
op = JournalOperations.EDIT_SUGG_ORG;
message = "Metadata updated";
}
@ -481,13 +477,12 @@ public class DatabaseUtils {
final List<OrganizationView> views =
similarIds.stream().map(organizationViewRepository::findById).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
final List<OrganizationView> persistents = views.stream().filter(v -> v.getPersistent()).collect(Collectors.toList());
final List<OrganizationView> persistents = views.stream().filter(OrganizationView::getPersistent).collect(Collectors.toList());
final OrganizationView masterOrg = new OrganizationView();
if (persistents.size() > 1) {
throw new RuntimeException("Too many persintent organizations");
} else if (persistents.size() == 1) {
if (persistents.size() > 1) { throw new RuntimeException("Too many persintent organizations"); }
if (persistents.size() == 1) {
backupOrg(persistents.get(0), user);
masterOrg.setId(persistents.get(0).getId());
masterOrg.setStatus(OrganizationStatus.approved.toString());
@ -570,7 +565,7 @@ public class DatabaseUtils {
final List<OpenaireDuplicate> newDuplicates = orgs.stream()
.map(OrganizationView::getId)
.map(openaireDuplicateRepository::findByLocalId)
.flatMap(l -> l.stream())
.flatMap(List::stream)
.map(d -> new OpenaireDuplicate(masterId, d.getOaOriginalId(), d.getRelType(), d.getOaCollectedFrom()))
.collect(Collectors.toList());
@ -633,11 +628,11 @@ public class DatabaseUtils {
}
private <T> Set<T> findAll(final List<OrganizationView> views, final Function<OrganizationView, Set<T>> mapper) {
return views.stream().map(mapper).flatMap(s -> s.stream()).collect(Collectors.toCollection(LinkedHashSet::new));
return views.stream().map(mapper).flatMap(Set::stream).collect(Collectors.toCollection(LinkedHashSet::new));
}
private <T> Set<T> findAll(final List<OrganizationView> views, final Function<OrganizationView, Set<T>> mapper, final Predicate<T> filter) {
return views.stream().map(mapper).flatMap(s -> s.stream()).filter(filter).collect(Collectors.toCollection(LinkedHashSet::new));
return views.stream().map(mapper).flatMap(Set::stream).filter(filter).collect(Collectors.toCollection(LinkedHashSet::new));
}
private List<Relationship> hideConflictOrgs(final String masterId, final String otherId) {
@ -679,13 +674,67 @@ public class DatabaseUtils {
if (valid) {
persistentOrganizationRepository.save(new PersistentOrganization(ooid));
return ooid;
} else {
throw new RuntimeException("The ID does not refer to an approved Organization");
}
throw new RuntimeException("The ID does not refer to an approved Organization");
}
public void deletePersistentOrgs(final String id) {
persistentOrganizationRepository.deleteById(id);
}
public List<String> obtainLogEntries(final int year, final int month, final String country) {
final String query = "SELECT o.id, o.name, j.operation, j.description, date(j.op_date) as op_date "
+ "FROM organizations o JOIN journal j ON o.id = j.id "
+ "WHERE o.country=? AND extract(year FROM j.op_date)=? AND extract(month FROM j.op_date)=? "
+ "ORDER BY date(j.op_date), o,id";
return jdbcTemplate.queryForList(query, country, year, month)
.stream()
.map(this::asLogEntry)
.collect(Collectors.toList());
}
private String asLogEntry(final Map<String, Object> map) {
final JournalOperations op = JournalOperations.valueOf("" + map.get("operation"));
final StringWriter sw = new StringWriter();
sw.write("On %s a curator ");
switch (op) {
case APPROVE_SUGG_ORG:
sw.write("approved the suggested organisation %s (ID: %s)");
break;
case NEW_ORG:
sw.write("created the organisation %s (ID: %s)");
break;
case NEW_SUGG_ORG:
sw.write("suggested the organisation %s (ID: %s)");
break;
case EDIT_ORG:
sw.write("updated the metadata of %s (ID: %s)");
break;
case EDIT_SUGG_ORG:
sw.write("updated the metadata of the suggested organisation %s (ID: %s)");
break;
case DUPLICATES:
sw.write("updated the list of duplicates for %s (ID: %s)");
break;
case FIX_CONFLICT:
sw.write("chose as master the organisation %s (id: %s), which has been involved in a conflict resolution");
break;
case NO_CONFLICT:
sw.write("stated that the conflict where the organisation %s (id: %s) was involved was a false positive");
break;
case BACKUP_ORG:
sw.write("resolved a conflict and the organisation %s (id: %s) involved has been hidden, because another organisation has been chosen as master");
break;
case UNKNOWN:
default:
sw.write("performed an unknown operation on %s (ID: %s)");
}
sw.write(". \"%s\"\n");
return String.format(sw.toString(), map.get("op_date"), map.get("name"), map.get("id"), map.get("description"));
}
}

View File

@ -749,4 +749,15 @@ $$;
CREATE TRIGGER insert_or_update_index_search_trigger AFTER INSERT OR UPDATE ON organizations FOR EACH ROW EXECUTE PROCEDURE insert_or_update_index_search_trigger();
CREATE TRIGGER delete_index_search_trigger BEFORE DELETE ON organizations FOR EACH ROW EXECUTE PROCEDURE delete_index_search();
-- PUBLIC VIEW
CREATE VIEW api_journal_view AS SELECT
o.id AS org_id,
o.name AS org_name,
o.country AS country,
j.operation AS operation,
j.description AS description,
j.op_date AS op_date
FROM organizations o JOIN journal j ON o.id = j.id;