added dnet-orgs-database-application

This commit is contained in:
Michele Artini 2020-07-03 12:09:22 +02:00
parent 6b32df9c7a
commit d1a4bd3668
373 changed files with 18691 additions and 0 deletions

View File

@ -0,0 +1,5 @@
1) Completare operazioni di insert/update sul db
2) Migliorare ricerca (utilizzare fulltext search di postgres)
3) Primo caricamento di organizzazioni da openaire (con prefisso openorgs____) con simrels
4) Caricamenti successivi di nuove oganizazzioni
5) Caricamenti successivi di nuove simrels

View File

@ -0,0 +1,3 @@
{"type_source": "SVN", "goal": "package -U source:jar",
"url": "http://svn-public.driver.research-infrastructures.eu/driver/dnet45/modules/dnet-orgs-database-application/trunk/", "deploy_repository": "dnet45-snapshots", "version": "4", "mail": "sandro.labruzzo@isti.cnr.it,michele.artini@isti.cnr.it, claudio.atzori@isti.cnr.it, alessia.bardi@isti.cnr.it",
"deploy_repository_url": "http://maven.research-infrastructures.eu/nexus/content/repositories/dnet45-snapshots", "name": "dnet-orgs-database-application"}

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>eu.dnetlib</groupId>
<artifactId>apps</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dnet-orgs-database-application</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>2.3.5</version>
</dependency>
<!-- hot swapping, disable cache for template, enable live reload -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- Swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,46 @@
package eu.dnetlib.organizations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@SpringBootApplication
@EnableSwagger2
@EnableCaching
public class MainApplication {
private static final Logger log = LoggerFactory.getLogger(MainApplication.class);
public static void main(final String[] args) {
SpringApplication.run(MainApplication.class, args);
}
@Bean
public static Docket newSwaggerDocket() {
log.info("Initializing SWAGGER...");
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(p -> p.startsWith("/api/"))
.build().apiInfo(new ApiInfoBuilder()
.title("D-Net Organizations Service APIs")
.description("APIs documentation")
.version("1.1")
.contact(ApiInfo.DEFAULT_CONTACT)
.license("Apache 2.0")
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0")
.build());
}
}

View File

@ -0,0 +1,41 @@
package eu.dnetlib.organizations;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import eu.dnetlib.organizations.controller.UserInfo;
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
private static Logger logger = LoggerFactory.getLogger(MyAccessDeniedHandler.class);
@Override
public void handle(final HttpServletRequest req, final HttpServletResponse res, final AccessDeniedException e)
throws IOException, ServletException {
final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
logger.warn(String.format("User '%s' attempted to access the protected URL: %s", auth.getName(), req.getRequestURI()));
}
if (UserInfo.isNotAuthorized(auth)) {
res.sendRedirect(req.getContextPath() + "/authorizationRequest");
} else {
res.sendRedirect(req.getContextPath() + "/alreadyRegistered");
}
}
}

View File

@ -0,0 +1,72 @@
package eu.dnetlib.organizations;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import eu.dnetlib.organizations.controller.UserRole;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Override
protected void configure(final HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/", "/api/**")
.hasAnyRole(UserRole.ADMIN.name(), UserRole.NATIONAL_ADMIN.name(), UserRole.USER.name())
.antMatchers("/registration_api/**")
.hasRole(UserRole.NOT_AUTHORIZED.name())
.antMatchers("/resources/**", "/webjars/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll()
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler);
}
@Autowired
public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select ?, '{MD5}60c4a0eb167dd41e915a885f582414df', true") // TODO: this is a MOCK, the user should
// be authenticated using the openaire
// credentials
.authoritiesByUsernameQuery("with const as (SELECT ? as email) "
+ "select c.email, 'ROLE_'||coalesce(u.role, '"
+ UserRole.NOT_AUTHORIZED
+ "') from const c left outer join users u on (u.email = c.email)");
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}

View File

@ -0,0 +1,36 @@
package eu.dnetlib.organizations.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "home";
}
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/authorizationRequest")
public String authorizationRequest() {
return "authorizationRequest";
}
@GetMapping("/alreadyRegistered")
public String alreadyRegistered() {
return "alreadyRegistered";
}
@RequestMapping(value = { "/doc", "/swagger" }, method = RequestMethod.GET)
public String apiDoc() {
return "redirect:swagger-ui.html";
}
}

View File

@ -0,0 +1,270 @@
package eu.dnetlib.organizations.controller;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import eu.dnetlib.organizations.model.OpenaireDuplicate;
import eu.dnetlib.organizations.model.Relationship;
import eu.dnetlib.organizations.model.utils.BrowseEntry;
import eu.dnetlib.organizations.model.utils.OrganizationConflict;
import eu.dnetlib.organizations.model.utils.OrganizationConflictImpl;
import eu.dnetlib.organizations.model.view.ConflictGroupView;
import eu.dnetlib.organizations.model.view.DuplicateGroupView;
import eu.dnetlib.organizations.model.view.OrganizationInfoView;
import eu.dnetlib.organizations.model.view.OrganizationSimpleView;
import eu.dnetlib.organizations.model.view.OrganizationView;
import eu.dnetlib.organizations.repository.OpenaireConflictRepository;
import eu.dnetlib.organizations.repository.OpenaireDuplicateRepository;
import eu.dnetlib.organizations.repository.UserCountryRepository;
import eu.dnetlib.organizations.repository.readonly.ConflictGroupViewRepository;
import eu.dnetlib.organizations.repository.readonly.DuplicateGroupViewRepository;
import eu.dnetlib.organizations.repository.readonly.OrganizationInfoViewRepository;
import eu.dnetlib.organizations.repository.readonly.OrganizationSimpleViewRepository;
import eu.dnetlib.organizations.repository.readonly.OrganizationViewRepository;
import eu.dnetlib.organizations.repository.readonly.SuggestionInfoViewByCountryRepository;
import eu.dnetlib.organizations.utils.DatabaseUtils;
import eu.dnetlib.organizations.utils.RelationType;
@RestController
@RequestMapping("/api/organizations")
public class OrganizationController {
@Autowired
private OrganizationViewRepository organizationViewRepository;
@Autowired
private OrganizationInfoViewRepository organizationInfoViewRepository;
@Autowired
private OrganizationSimpleViewRepository organizationSimpleViewRepository;
@Autowired
private OpenaireDuplicateRepository openaireDuplicateRepository;
@Autowired
private OpenaireConflictRepository openaireConflictRepository;
@Autowired
private ConflictGroupViewRepository conflictGroupViewRepository;
@Autowired
private SuggestionInfoViewByCountryRepository suggestionInfoViewByCountryRepository;
@Autowired
private UserCountryRepository userCountryRepository;
@Autowired
private DuplicateGroupViewRepository duplicateGroupViewRepository;
@Autowired
private DatabaseUtils databaseUtils;
@RequestMapping(value = "/save", method = RequestMethod.POST)
public List<String> save(@RequestBody final OrganizationView org, final Authentication authentication) {
if (StringUtils.isBlank(org.getName())) {
throw new RuntimeException("Missing field: name");
} else if (StringUtils.isBlank(org.getCountry())) {
throw new RuntimeException("Missing field: country");
} else if (StringUtils.isBlank(org.getType())) {
throw new RuntimeException("Missing field: type");
} else if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForCountry(org.getCountry(), authentication.getName())) {
final String orgId = databaseUtils.insertOrUpdateOrganization(org, authentication.getName(), StringUtils.isNotBlank(org.getId()));
return Arrays.asList(orgId);
} else {
throw new RuntimeException("User not authorized");
}
}
@RequestMapping(value = "/info", method = RequestMethod.GET)
public OrganizationInfoView infoById(@RequestParam final String id, final Authentication authentication) {
return organizationInfoViewRepository.findById(id).get();
}
@RequestMapping(value = "/suggestionsInfo", method = RequestMethod.GET)
public SuggestionInfo suggestionsInfo(final Authentication authentication) {
final SuggestionInfo info = new SuggestionInfo();
if (UserInfo.isSuperAdmin(authentication)) {
suggestionInfoViewByCountryRepository.findAll().forEach(info::add);
} else if (UserInfo.isSimpleUser(authentication) || UserInfo.isNationalAdmin(authentication)) {
userCountryRepository.getCountriesForUser(authentication.getName())
.stream()
.map(suggestionInfoViewByCountryRepository::findById)
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(info::add);
}
return info;
}
@RequestMapping(value = "/get", method = RequestMethod.GET)
public OrganizationView findById(@RequestParam final String id, final Authentication authentication) {
final OrganizationView org = organizationViewRepository.findById(id).get();
if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForCountry(org.getCountry(), authentication.getName())) {
return org;
} else {
throw new RuntimeException("User not authorized");
}
}
@RequestMapping(value = "/conflicts", method = RequestMethod.GET)
public List<OrganizationConflict> conflicts(@RequestParam final String id, final Authentication authentication) {
if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForId(id, authentication.getName())) {
return openaireConflictRepository.getConflictsForId(id);
} else {
throw new RuntimeException("User not authorized");
}
}
@RequestMapping(value = "/duplicates", method = RequestMethod.GET)
public List<OpenaireDuplicate> duplicates(@RequestParam final String id, final Authentication authentication) {
if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForId(id, authentication.getName())) {
return openaireDuplicateRepository.findByLocalId(id);
} else {
throw new RuntimeException("User not authorized");
}
}
@RequestMapping(value = "/conflicts/byCountry/{country}", method = RequestMethod.GET)
public Collection<Set<OrganizationConflictImpl>> findConflictsByCountry(@PathVariable final String country, final Authentication authentication) {
databaseUtils.verifyConflictGroups(false);
if (UserInfo.isSuperAdmin(authentication)) {
return groupConflicts(conflictGroupViewRepository.findByCountry1OrCountry2(country, country).stream());
} else if (UserInfo.isSimpleUser(authentication) || UserInfo.isNationalAdmin(authentication)) {
final Stream<ConflictGroupView> list = userCountryRepository.getCountriesForUser(authentication.getName())
.stream()
.filter(country::equalsIgnoreCase)
.map(c -> conflictGroupViewRepository.findByCountry1OrCountry2(c, c).stream())
.findFirst()
.orElse(Stream.empty());
return groupConflicts(list);
} else {
throw new RuntimeException("User not authorized");
}
}
@RequestMapping(value = "/duplicates/byCountry/{country}", method = RequestMethod.GET)
public Iterable<DuplicateGroupView> findDuplicatesByCountry(@PathVariable final String country, final Authentication authentication) {
if (UserInfo.isSuperAdmin(authentication)) {
return duplicateGroupViewRepository.findByCountry(country);
} else if (UserInfo.isSimpleUser(authentication) || UserInfo.isNationalAdmin(authentication)) {
return userCountryRepository.getCountriesForUser(authentication.getName())
.stream()
.filter(country::equalsIgnoreCase)
.map(duplicateGroupViewRepository::findByCountry)
.findFirst()
.orElse(new ArrayList<DuplicateGroupView>());
} else {
throw new RuntimeException("User not authorized");
}
}
private Collection<Set<OrganizationConflictImpl>> groupConflicts(final Stream<ConflictGroupView> stream) {
final Map<String, Set<OrganizationConflictImpl>> map = new TreeMap<>();
stream.forEach(c -> {
if (!map.containsKey(c.getGroup())) {
map.put(c.getGroup(), new TreeSet<>());
}
map.get(c.getGroup()).add(new OrganizationConflictImpl(c.getId1(), c.getName1(), c.getType1(), c.getCity1(), c.getCountry1()));
map.get(c.getGroup()).add(new OrganizationConflictImpl(c.getId2(), c.getName2(), c.getType2(), c.getCity2(), c.getCountry2()));
});
return map.values();
}
@RequestMapping(value = "/duplicates", method = RequestMethod.POST)
public List<OpenaireDuplicate> duplicates(@RequestBody final List<OpenaireDuplicate> simrels, final Authentication authentication) {
final boolean b = UserInfo.isSuperAdmin(authentication)
|| simrels.stream()
.map(OpenaireDuplicate::getLocalId)
.distinct()
.allMatch(id -> userCountryRepository.verifyAuthorizationForId(id, authentication.getName()));
if (b) {
return openaireDuplicateRepository.saveAll(simrels);
} else {
throw new RuntimeException("User not authorized");
}
}
@RequestMapping(value = "/search/{page}/{size}", method = RequestMethod.GET)
public Page<OrganizationSimpleView> search(@PathVariable final int page,
@PathVariable final int size,
@RequestParam final String q,
final Authentication authentication) {
return UserInfo.isSuperAdmin(authentication)
? organizationSimpleViewRepository.findByNameContainingIgnoreCase(q, PageRequest.of(page, size))
: organizationSimpleViewRepository.findByNameForUser(q, authentication.getName(), PageRequest.of(page, size));
}
@RequestMapping(value = "/byCountry/{code}/{page}/{size}", method = RequestMethod.GET)
public Page<OrganizationSimpleView> findByCountry(@PathVariable final String code,
@PathVariable final int page,
@PathVariable final int size,
final Authentication authentication) {
if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForCountry(code, authentication.getName())) {
return organizationSimpleViewRepository.findByCountry(code, PageRequest.of(page, size));
} else {
throw new RuntimeException("User not authorized");
}
}
@RequestMapping(value = "/byType/{type}/{page}/{size}", method = RequestMethod.GET)
public Page<OrganizationSimpleView> findByType(@PathVariable final String type,
@PathVariable final int page,
@PathVariable final int size,
final Authentication authentication) {
return UserInfo.isSuperAdmin(authentication)
? organizationSimpleViewRepository.findByType(type, PageRequest.of(page, size))
: organizationSimpleViewRepository.findByTypeForUser(type, authentication.getName(), PageRequest.of(page, size));
}
@RequestMapping(value = "/browse/countries", method = RequestMethod.GET)
public List<BrowseEntry> browseCountries(final Authentication authentication) {
return UserInfo.isSuperAdmin(authentication)
? organizationSimpleViewRepository.browseCountries()
: organizationSimpleViewRepository.browseCountriesForUser(authentication.getName());
}
@RequestMapping(value = "/browse/types", method = RequestMethod.GET)
public List<BrowseEntry> browseOrganizationTypes(final Authentication authentication) {
return UserInfo.isSuperAdmin(authentication)
? organizationSimpleViewRepository.browseTypes()
: organizationSimpleViewRepository.browseTypesForUser(authentication.getName());
}
@RequestMapping(value = "/conflicts/fix/{masterId}", method = RequestMethod.POST)
public List<Relationship> fixConflicts(final Authentication authentication, @PathVariable final String masterId, @RequestBody final List<String> otherIds) {
if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForId(masterId, authentication.getName())) {
return otherIds.stream()
.filter(id -> UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForId(id, authentication.getName()))
.map(id -> databaseUtils.makeRelation(masterId, id, RelationType.Merges))
.flatMap(List::stream)
.collect(Collectors.toList());
} else {
return new ArrayList<>();
}
}
}

View File

@ -0,0 +1,53 @@
package eu.dnetlib.organizations.controller;
import java.util.LinkedHashMap;
import java.util.Map;
import eu.dnetlib.organizations.model.view.SuggestionInfoViewByCountry;
public class SuggestionInfo {
public class SuggestionCounter {
private long nDuplicates = 0;
private long nConflicts = 0;
public long getnDuplicates() {
return nDuplicates;
}
public void setnDuplicates(final long nDuplicates) {
this.nDuplicates = nDuplicates;
}
public long getnConflicts() {
return nConflicts;
}
public void setnConflicts(final long nConflicts) {
this.nConflicts = nConflicts;
}
public void add(final SuggestionInfoViewByCountry infoCountry) {
nDuplicates += infoCountry.getnDuplicates();
nConflicts += infoCountry.getnConflicts();
}
}
public SuggestionCounter total = new SuggestionCounter();;
public Map<String, SuggestionCounter> byCountry = new LinkedHashMap<>();
public void add(final SuggestionInfoViewByCountry infoCountry) {
final String country = infoCountry.getCountry();
if (!byCountry.containsKey(country)) {
byCountry.put(country, new SuggestionCounter());
}
byCountry.get(country).add(infoCountry);
total.add(infoCountry);
}
}

View File

@ -0,0 +1,85 @@
package eu.dnetlib.organizations.controller;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import eu.dnetlib.organizations.model.view.UserView;
import eu.dnetlib.organizations.repository.UserRepository;
import eu.dnetlib.organizations.repository.readonly.UserViewRepository;
import eu.dnetlib.organizations.utils.DatabaseUtils;
@RestController
public class UserController {
@Autowired
private UserRepository userRepository;
@Autowired
private UserViewRepository userViewRepository;
@Autowired
private DatabaseUtils dbUtils;
@PostMapping(value = "/registration_api/newUser")
public Map<String, Integer> newUser(final @RequestBody List<String> countries, final Authentication authentication) {
final String email = authentication.getName();
final Map<String, Integer> res = new HashMap<>();
if (!UserInfo.isNotAuthorized(authentication) || userRepository.existsById(email)) {
res.put("status", 2);
} else {
dbUtils.newUser(email, countries);
res.put("status", 1);
}
return res;
}
@GetMapping("/api/users")
public Iterable<UserView> users(final Authentication authentication) {
if (UserInfo.isSuperAdmin(authentication)) {
return userViewRepository.findAll();
} else if (UserInfo.isNationalAdmin(authentication)) {
// IMPORTANT: a national admin can manage ONLY the users where ALL the countries are under his control
final List<UserView> res = new ArrayList<>();
final List<String> myCountries = dbUtils.listCountriesForUser(authentication.getName());
for (final UserView uw : userViewRepository.findAll()) {
if (uw.getCountries() != null && uw.getCountries().length > 0 && myCountries.containsAll(Arrays.asList(uw.getCountries()))) {
res.add(uw);
}
}
return res;
} else {
return new ArrayList<>();
}
}
@PostMapping("/api/users")
public Iterable<UserView> save(@RequestBody final UserView userView, final Authentication authentication) {
if (authentication.getName().equals(userView.getEmail())) { throw new RuntimeException("You can't edit your own user"); }
dbUtils.saveUser(userView);
return users(authentication);
}
@DeleteMapping("/api/users")
public Iterable<UserView> delete(final @RequestParam String email, final Authentication authentication) {
if (authentication.getName().equals(email)) { throw new RuntimeException("You can't delete your own user"); }
dbUtils.deleteUser(email);
return users(authentication);
}
}

View File

@ -0,0 +1,89 @@
package eu.dnetlib.organizations.controller;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
public class UserInfo {
private String name;
private UserRole role;
public UserInfo() {
this.name = "anonymous";
this.role = UserRole.NOT_AUTHORIZED;
}
public UserInfo(final String name, final UserRole role) {
this.name = name;
this.role = role;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public UserRole getRole() {
return role;
}
public void setRole(final UserRole role) {
this.role = role;
}
public static UserInfo generate(final Authentication authentication) {
return new UserInfo(authentication.getName(), findRole(authentication));
}
public static UserRole findRole(final Authentication authentication) {
return authentication.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.map(s -> StringUtils.substringAfter(s, "ROLE_"))
.filter(s -> EnumUtils.isValidEnum(UserRole.class, s))
.map(UserRole::valueOf)
.findFirst()
.orElseGet(() -> UserRole.NOT_AUTHORIZED);
}
public static boolean isSuperAdmin(final Authentication authentication) {
for (final GrantedAuthority aut : authentication.getAuthorities()) {
if (aut.getAuthority().equals("ROLE_" + UserRole.ADMIN)) { return true; }
}
return false;
}
public static boolean isNationalAdmin(final Authentication authentication) {
for (final GrantedAuthority aut : authentication.getAuthorities()) {
if (aut.getAuthority().equals("ROLE_" + UserRole.NATIONAL_ADMIN)) { return true; }
}
return false;
}
public static boolean isSimpleUser(final Authentication authentication) {
for (final GrantedAuthority aut : authentication.getAuthorities()) {
if (aut.getAuthority().equals("ROLE_" + UserRole.USER)) { return true; }
}
return false;
}
public static boolean isPending(final Authentication authentication) {
for (final GrantedAuthority aut : authentication.getAuthorities()) {
if (aut.getAuthority().equals("ROLE_" + UserRole.PENDING)) { return true; }
}
return false;
}
public static boolean isNotAuthorized(final Authentication authentication) {
for (final GrantedAuthority aut : authentication.getAuthorities()) {
if (aut.getAuthority().equals("ROLE_" + UserRole.NOT_AUTHORIZED)) { return true; }
}
return false;
}
}

View File

@ -0,0 +1,5 @@
package eu.dnetlib.organizations.controller;
public enum UserRole {
USER, ADMIN, NATIONAL_ADMIN, PENDING, NOT_AUTHORIZED
}

View File

@ -0,0 +1,52 @@
package eu.dnetlib.organizations.controller;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import eu.dnetlib.organizations.utils.DatabaseUtils;
import eu.dnetlib.organizations.utils.DatabaseUtils.VocabularyTable;
import eu.dnetlib.organizations.utils.RelationType;
import eu.dnetlib.organizations.utils.SimilarityType;
@RestController
public class VocabulariesController {
@Autowired
private DatabaseUtils databaseUtils;
@GetMapping("/api/vocabularies")
public Map<String, List<String>> ListVocabularies(final Authentication authentication) {
final Map<String, List<String>> vocs = new HashMap<>();
vocs.put("orgTypes", databaseUtils.listValuesOfVocabularyTable(VocabularyTable.org_types));
vocs.put("idTypes", databaseUtils.listValuesOfVocabularyTable(VocabularyTable.id_types));
vocs.put("languages", databaseUtils.listValuesOfVocabularyTable(VocabularyTable.languages));
vocs.put("relTypes", Arrays.stream(RelationType.values()).map(Object::toString).collect(Collectors.toList()));
vocs.put("similaritiesType", Arrays.stream(SimilarityType.values()).map(Object::toString).collect(Collectors.toList()));
if (UserInfo.isSimpleUser(authentication) || UserInfo.isNationalAdmin(authentication)) {
vocs.put("countries", databaseUtils.listCountriesForUser(authentication.getName()));
} else if (UserInfo.isSuperAdmin(authentication)) {
vocs.put("countries", databaseUtils.listValuesOfVocabularyTable(VocabularyTable.countries));
} else {
vocs.put("countries", new ArrayList<String>());
}
return vocs;
}
@GetMapping({ "/api/voc/allCountries", "/registration_api/voc/allCountries" })
public List<String> allCountries() {
return databaseUtils.listValuesOfVocabularyTable(VocabularyTable.countries);
}
}

View File

@ -0,0 +1,52 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
@Entity
@Table(name = "acronyms")
@IdClass(AcronymPK.class)
public class Acronym implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1521008848879976517L;
@Id
@Column(name = "id")
private String orgId;
@Id
@Column(name = "acronym")
private String acronym;
public Acronym() {}
public Acronym(final String orgId, final String acronym) {
this.orgId = orgId;
this.acronym = acronym;
}
public String getOrgId() {
return orgId;
}
public void setOrgId(final String orgId) {
this.orgId = orgId;
}
public String getAcronym() {
return acronym;
}
public void setAcronym(final String acronym) {
this.acronym = acronym;
}
}

View File

@ -0,0 +1,51 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import java.util.Objects;
public class AcronymPK implements Serializable {
/**
*
*/
private static final long serialVersionUID = -7771532658881429064L;
private String orgId;
private String acronym;
public String getOrgId() {
return orgId;
}
public void setOrgId(final String orgId) {
this.orgId = orgId;
}
public String getAcronym() {
return acronym;
}
public void setAcronym(final String acronym) {
this.acronym = acronym;
}
@Override
public int hashCode() {
return Objects.hash(acronym, orgId);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (!(obj instanceof AcronymPK)) { return false; }
final AcronymPK other = (AcronymPK) obj;
return Objects.equals(acronym, other.acronym) && Objects.equals(orgId, other.orgId);
}
@Override
public String toString() {
return String.format("AcronymPK [orgId=%s, acronym=%s]", orgId, acronym);
}
}

View File

@ -0,0 +1,76 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
@Entity
@Table(name = "oa_conflicts")
@IdClass(OpenaireConflictPK.class)
public class OpenaireConflict implements Serializable {
/**
*
*/
private static final long serialVersionUID = -5143344657695218270L;
@Id
@Column(name = "id1")
private String id1;
@Id
@Column(name = "id2")
private String id2;
@Column(name = "reltype")
private String relType;
@Column(name = "idgroup")
private String group;
public OpenaireConflict() {}
public OpenaireConflict(final String id1, final String id2, final String relType, final String group) {
this.id1 = id1;
this.id2 = id2;
this.relType = relType;
this.group = group;
}
public String getId1() {
return id1;
}
public void setId1(final String id1) {
this.id1 = id1;
}
public String getId2() {
return id2;
}
public void setId2(final String id2) {
this.id2 = id2;
}
public String getRelType() {
return relType;
}
public void setRelType(final String relType) {
this.relType = relType;
}
public String getGroup() {
return group;
}
public void setGroup(final String group) {
this.group = group;
}
}

View File

@ -0,0 +1,59 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import java.util.Objects;
public class OpenaireConflictPK implements Serializable {
/**
*
*/
private static final long serialVersionUID = 6752695921988906962L;
private String id1;
private String id2;
public OpenaireConflictPK() {}
public OpenaireConflictPK(final String id1, final String id2) {
this.id1 = id1;
this.id2 = id2;
}
public String getId1() {
return id1;
}
public void setId1(final String id1) {
this.id1 = id1;
}
public String getId2() {
return id2;
}
public void setId2(final String id2) {
this.id2 = id2;
}
@Override
public int hashCode() {
return Objects.hash(id1, id2);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (!(obj instanceof OpenaireConflictPK)) { return false; }
final OpenaireConflictPK other = (OpenaireConflictPK) obj;
return Objects.equals(id1, other.id1) && Objects.equals(id2, other.id2);
}
@Override
public String toString() {
return String.format("OpenaireConflictPK [id1=%s, id2=%s]", id1, id2);
}
}

View File

@ -0,0 +1,111 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
@Entity
@Table(name = "oa_duplicates")
@IdClass(OpenaireDuplicatePK.class)
public class OpenaireDuplicate implements Serializable {
/**
*
*/
private static final long serialVersionUID = -5656204073481770618L;
@Id
@Column(name = "local_id")
private String localId;
@Id
@Column(name = "oa_original_id")
private String oaOriginalId;
@Column(name = "oa_name")
private String oaName;
@Column(name = "oa_acronym")
private String oaAcronym;
@Column(name = "oa_country")
private String oaCountry;
@Column(name = "oa_url")
private String oaUrl;
@Column(name = "oa_collectedfrom")
private String oaCollectedFrom;
@Column(name = "reltype")
private String relType;
public String getLocalId() {
return localId;
}
public void setLocalId(final String localId) {
this.localId = localId;
}
public String getOaOriginalId() {
return oaOriginalId;
}
public void setOaOriginalId(final String oaOriginalId) {
this.oaOriginalId = oaOriginalId;
}
public String getOaName() {
return oaName;
}
public void setOaName(final String oaName) {
this.oaName = oaName;
}
public String getOaAcronym() {
return oaAcronym;
}
public void setOaAcronym(final String oaAcronym) {
this.oaAcronym = oaAcronym;
}
public String getOaCountry() {
return oaCountry;
}
public void setOaCountry(final String oaCountry) {
this.oaCountry = oaCountry;
}
public String getOaUrl() {
return oaUrl;
}
public void setOaUrl(final String oaUrl) {
this.oaUrl = oaUrl;
}
public String getOaCollectedFrom() {
return oaCollectedFrom;
}
public void setOaCollectedFrom(final String oaCollectedFrom) {
this.oaCollectedFrom = oaCollectedFrom;
}
public String getRelType() {
return relType;
}
public void setRelType(final String relType) {
this.relType = relType;
}
}

View File

@ -0,0 +1,51 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import java.util.Objects;
public class OpenaireDuplicatePK implements Serializable {
/**
*
*/
private static final long serialVersionUID = 2546257975404466712L;
private String localId;
private String oaOriginalId;
public String getLocalId() {
return localId;
}
public void setLocalId(final String localId) {
this.localId = localId;
}
public String getOaOriginalId() {
return oaOriginalId;
}
public void setOaOriginalId(final String oaOriginalId) {
this.oaOriginalId = oaOriginalId;
};
@Override
public int hashCode() {
return Objects.hash(localId, oaOriginalId);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (!(obj instanceof OpenaireDuplicatePK)) { return false; }
final OpenaireDuplicatePK other = (OpenaireDuplicatePK) obj;
return Objects.equals(localId, other.localId) && Objects.equals(oaOriginalId, other.oaOriginalId);
}
@Override
public String toString() {
return String.format("OpenaireDuplicatePK [localId=%s, oaOriginalId=%s]", localId, oaOriginalId);
}
}

View File

@ -0,0 +1,112 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "organizations")
public class Organization implements Serializable {
/**
*
*/
private static final long serialVersionUID = 5563256467964495301L;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
@Column(name = "name")
private String name;
@Column(name = "type")
private String type;
@Column(name = "lat")
private Double lat;
@Column(name = "lng")
private Double lng;
@Column(name = "city")
private String city;
@Column(name = "country")
private String country;
public Organization() {}
public Organization(final String id, final String name, final String type, final Double lat, final Double lng, final String city, final String country) {
this.id = id;
this.name = name;
this.type = type;
this.lat = lat;
this.lng = lng;
this.city = city;
this.country = country;
}
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 getType() {
return type;
}
public void setType(final String type) {
this.type = type;
}
public Double getLat() {
return lat;
}
public void setLat(final Double lat) {
this.lat = lat;
}
public Double getLng() {
return lng;
}
public void setLng(final Double lng) {
this.lng = lng;
}
public String getCity() {
return city;
}
public void setCity(final String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(final String country) {
this.country = country;
}
}

View File

@ -0,0 +1,65 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
@Entity
@Table(name = "other_ids")
@IdClass(OtherIdentifierPK.class)
public class OtherIdentifier implements Serializable {
/**
*
*/
private static final long serialVersionUID = -8290245353090885241L;
@Id
@Column(name = "id")
private String orgId;
@Id
@Column(name = "otherid")
private String otherId;
@Id
@Column(name = "type")
private String type;
public OtherIdentifier() {}
public OtherIdentifier(final String orgId, final String otherId, final String type) {
this.orgId = orgId;
this.otherId = otherId;
this.type = type;
}
public String getOrgId() {
return orgId;
}
public void setOrgId(final String orgId) {
this.orgId = orgId;
}
public String getOtherId() {
return otherId;
}
public void setOtherId(final String otherId) {
this.otherId = otherId;
}
public String getType() {
return type;
}
public void setType(final String type) {
this.type = type;
}
}

View File

@ -0,0 +1,60 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import java.util.Objects;
public class OtherIdentifierPK implements Serializable {
/**
*
*/
private static final long serialVersionUID = -3525128185006173748L;
private String orgId;
private String otherId;
private String type;
public String getOrgId() {
return orgId;
}
public void setOrgId(final String orgId) {
this.orgId = orgId;
}
public String getOtherId() {
return otherId;
}
public void setOtherId(final String otherId) {
this.otherId = otherId;
}
public String getType() {
return type;
}
public void setType(final String type) {
this.type = type;
}
@Override
public int hashCode() {
return Objects.hash(orgId, otherId, type);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (!(obj instanceof OtherIdentifierPK)) { return false; }
final OtherIdentifierPK other = (OtherIdentifierPK) obj;
return Objects.equals(orgId, other.orgId) && Objects.equals(otherId, other.otherId) && Objects.equals(type, other.type);
}
@Override
public String toString() {
return String.format("OtherIdentifierPK [orgId=%s, otherId=%s, type=%s]", orgId, otherId, type);
}
}

View File

@ -0,0 +1,64 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
@Entity
@Table(name = "other_names")
@IdClass(OtherNamePK.class)
public class OtherName implements Serializable {
/**
*
*/
private static final long serialVersionUID = -5212012138309835912L;
@Id
@Column(name = "id")
private String orgId;
@Id
@Column(name = "name")
private String name;
@Id
@Column(name = "lang")
private String lang;
public OtherName() {}
public OtherName(final String orgId, final String name, final String lang) {
this.orgId = orgId;
this.name = name;
this.lang = lang;
}
public String getOrgId() {
return orgId;
}
public void setOrgId(final String orgId) {
this.orgId = orgId;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public String getLang() {
return lang;
}
public void setLang(final String lang) {
this.lang = lang;
}
}

View File

@ -0,0 +1,62 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import java.util.Objects;
public class OtherNamePK implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1225063182881256637L;
private String orgId;
private String name;
private String lang;
public String getOrgId() {
return orgId;
}
public void setOrgId(final String orgId) {
this.orgId = orgId;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public String getLang() {
return lang;
}
public void setLang(final String lang) {
this.lang = lang;
}
@Override
public int hashCode() {
return Objects.hash(orgId, lang, name);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (!(obj instanceof OtherNamePK)) { return false; }
final OtherNamePK other = (OtherNamePK) obj;
return Objects.equals(orgId, other.orgId) && Objects.equals(lang, other.lang) && Objects.equals(name, other.name);
}
@Override
public String toString() {
return String.format("OtherNamePK [orgId=%s, name=%s, lang=%s]", orgId, name, lang);
}
}

View File

@ -0,0 +1,65 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
@Entity
@Table(name = "relationships")
@IdClass(RelationshipPK.class)
public class Relationship implements Serializable {
/**
*
*/
private static final long serialVersionUID = -5700143694178113214L;
@Id
@Column(name = "id1")
private String id1;
@Id
@Column(name = "id2")
private String id2;
@Id
@Column(name = "reltype")
private String relType;
public Relationship() {}
public Relationship(final String id1, final String id2, final String relType) {
this.id1 = id1;
this.id2 = id2;
this.relType = relType;
}
public String getId1() {
return id1;
}
public void setId1(final String id1) {
this.id1 = id1;
}
public String getId2() {
return id2;
}
public void setId2(final String id2) {
this.id2 = id2;
}
public String getRelType() {
return relType;
}
public void setRelType(final String relType) {
this.relType = relType;
}
}

View File

@ -0,0 +1,61 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import java.util.Objects;
public class RelationshipPK implements Serializable {
/**
*
*/
private static final long serialVersionUID = 6090016904833186505L;
private String id1;
private String id2;
private String relType;
public String getId1() {
return id1;
}
public void setId1(final String id1) {
this.id1 = id1;
}
public String getId2() {
return id2;
}
public void setId2(final String id2) {
this.id2 = id2;
}
public String getRelType() {
return relType;
}
public void setRelType(final String relType) {
this.relType = relType;
}
@Override
public int hashCode() {
return Objects.hash(id1, id2, relType);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (!(obj instanceof RelationshipPK)) { return false; }
final RelationshipPK other = (RelationshipPK) obj;
return Objects.equals(id1, other.id1) && Objects.equals(id2, other.id2) && Objects.equals(relType, other.relType);
}
@Override
public String toString() {
return String.format("RelationshipPK [id1=%s, id2=%s, relType=%s]", id1, id2, relType);
}
}

View File

@ -0,0 +1,52 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
@Entity
@Table(name = "urls")
@IdClass(UrlPK.class)
public class Url implements Serializable {
/**
*
*/
private static final long serialVersionUID = -5889020711423271565L;
@Id
@Column(name = "id")
private String orgId;
@Id
@Column(name = "url")
private String url;
public Url() {}
public Url(final String orgId, final String url) {
this.orgId = orgId;
this.url = url;
}
public String getOrgId() {
return orgId;
}
public void setOrgId(final String orgId) {
this.orgId = orgId;
}
public String getUrl() {
return url;
}
public void setUrl(final String url) {
this.url = url;
}
}

View File

@ -0,0 +1,51 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import java.util.Objects;
public class UrlPK implements Serializable {
/**
*
*/
private static final long serialVersionUID = 6459755341474103359L;
private String orgId;
private String url;
public String getOrgId() {
return orgId;
}
public void setOrgId(final String orgId) {
this.orgId = orgId;
}
public String getUrl() {
return url;
}
public void setUrl(final String url) {
this.url = url;
}
@Override
public int hashCode() {
return Objects.hash(orgId, url);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (!(obj instanceof UrlPK)) { return false; }
final UrlPK other = (UrlPK) obj;
return Objects.equals(orgId, other.orgId) && Objects.equals(url, other.url);
}
@Override
public String toString() {
return String.format("UrlPK [orgId=%s, urls=%s]", orgId, url);
}
}

View File

@ -0,0 +1,53 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "users")
public class User implements Serializable {
/**
*
*/
private static final long serialVersionUID = -9062674086098391604L;
@Id
@Column(name = "email")
private String email;
@Column(name = "valid")
private boolean valid;
@Column(name = "role")
private String role;
public String getEmail() {
return email;
}
public void setEmail(final String email) {
this.email = email;
}
public boolean isValid() {
return valid;
}
public void setValid(final boolean valid) {
this.valid = valid;
}
public String getRole() {
return role;
}
public void setRole(final String role) {
this.role = role;
}
}

View File

@ -0,0 +1,52 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
@Entity
@Table(name = "user_countries")
@IdClass(UserCountryPK.class)
public class UserCountry implements Serializable {
/**
*
*/
private static final long serialVersionUID = 3493897777214583701L;
@Id
@Column(name = "email")
private String email;
@Id
@Column(name = "country")
private String country;
public UserCountry() {}
public UserCountry(final String email, final String country) {
this.email = email;
this.country = country;
}
public String getEmail() {
return email;
}
public void setEmail(final String email) {
this.email = email;
}
public String getCountry() {
return country;
}
public void setCountry(final String country) {
this.country = country;
}
}

View File

@ -0,0 +1,52 @@
package eu.dnetlib.organizations.model;
import java.io.Serializable;
import java.util.Objects;
public class UserCountryPK implements Serializable {
/**
*
*/
private static final long serialVersionUID = 2003686130380208901L;
private String email;
private String country;
public String getEmail() {
return email;
}
public void setEmail(final String email) {
this.email = email;
}
public String getCountry() {
return country;
}
public void setCountry(final String country) {
this.country = country;
}
@Override
public int hashCode() {
return Objects.hash(country, email);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (!(obj instanceof UserCountryPK)) { return false; }
final UserCountryPK other = (UserCountryPK) obj;
return Objects.equals(country, other.country) && Objects.equals(email, other.email);
}
@Override
public String toString() {
return String.format("UserCountryPK [email=%s, country=%s]", email, country);
}
}

View File

@ -0,0 +1,9 @@
package eu.dnetlib.organizations.model.utils;
public interface BrowseEntry {
String getValue();
int getCount();
}

View File

@ -0,0 +1,15 @@
package eu.dnetlib.organizations.model.utils;
public interface OrganizationConflict {
String getId();
String getName();
String getType();
String getCity();
String getCountry();
}

View File

@ -0,0 +1,91 @@
package eu.dnetlib.organizations.model.utils;
import java.util.Objects;
public class OrganizationConflictImpl implements OrganizationConflict, Comparable<OrganizationConflict> {
private String id;
private String name;
private String type;
private String city;
private String country;
public OrganizationConflictImpl() {}
public OrganizationConflictImpl(final String id) {
this.id = id;
}
public OrganizationConflictImpl(final String id, final String name, final String type, final String city, final String country) {
this.id = id;
this.name = name;
this.type = type;
this.city = city;
this.country = country;
}
@Override
public String getId() {
return id;
}
public void setId(final String id) {
this.id = id;
}
@Override
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
@Override
public String getType() {
return type;
}
public void setType(final String type) {
this.type = type;
}
@Override
public String getCity() {
return city;
}
public void setCity(final String city) {
this.city = city;
}
@Override
public String getCountry() {
return country;
}
public void setCountry(final String country) {
this.country = country;
}
@Override
public int compareTo(final OrganizationConflict o) {
return getId().compareTo(o.getId());
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (!(obj instanceof OrganizationConflictImpl)) { return false; }
final OrganizationConflictImpl other = (OrganizationConflictImpl) obj;
return Objects.equals(id, other.id);
}
}

View File

@ -0,0 +1,144 @@
package eu.dnetlib.organizations.model.view;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
@Entity
@Table(name = "conflict_groups_view")
@IdClass(ConflictGroupViewPK.class)
public class ConflictGroupView implements Serializable {
/**
*
*/
private static final long serialVersionUID = -6621501199394187765L;
@Id
@Column(name = "id_1")
private String id1;
@Id
@Column(name = "id_2")
private String id2;
@Column(name = "name_1")
private String name1;
@Column(name = "name_2")
private String name2;
@Column(name = "type_1")
private String type1;
@Column(name = "type_2")
private String type2;
@Column(name = "city_1")
private String city1;
@Column(name = "city_2")
private String city2;
@Column(name = "country_1")
private String country1;
@Column(name = "country_2")
private String country2;
@Column(name = "idgroup")
private String group;
public String getId1() {
return id1;
}
public void setId1(final String id1) {
this.id1 = id1;
}
public String getId2() {
return id2;
}
public void setId2(final String id2) {
this.id2 = id2;
}
public String getName1() {
return name1;
}
public void setName1(final String name1) {
this.name1 = name1;
}
public String getName2() {
return name2;
}
public void setName2(final String name2) {
this.name2 = name2;
}
public String getType1() {
return type1;
}
public void setType1(final String type1) {
this.type1 = type1;
}
public String getType2() {
return type2;
}
public void setType2(final String type2) {
this.type2 = type2;
}
public String getCity1() {
return city1;
}
public void setCity1(final String city1) {
this.city1 = city1;
}
public String getCity2() {
return city2;
}
public void setCity2(final String city2) {
this.city2 = city2;
}
public String getCountry1() {
return country1;
}
public void setCountry1(final String country1) {
this.country1 = country1;
}
public String getCountry2() {
return country2;
}
public void setCountry2(final String country2) {
this.country2 = country2;
}
public String getGroup() {
return group;
}
public void setGroup(final String group) {
this.group = group;
}
}

View File

@ -0,0 +1,51 @@
package eu.dnetlib.organizations.model.view;
import java.io.Serializable;
import java.util.Objects;
public class ConflictGroupViewPK implements Serializable {
/**
*
*/
private static final long serialVersionUID = -4702103726315884367L;
private String id1;
private String id2;
public String getId1() {
return id1;
}
public void setId1(final String id1) {
this.id1 = id1;
}
public String getId2() {
return id2;
}
public void setId2(final String id2) {
this.id2 = id2;
}
@Override
public int hashCode() {
return Objects.hash(id1, id2);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (!(obj instanceof ConflictGroupViewPK)) { return false; }
final ConflictGroupViewPK other = (ConflictGroupViewPK) obj;
return Objects.equals(id1, other.id1) && Objects.equals(id2, other.id2);
}
@Override
public String toString() {
return String.format("ConflictGroupViewPK [id1=%s, id2=%s]", id1, id2);
}
}

View File

@ -0,0 +1,75 @@
package eu.dnetlib.organizations.model.view;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "duplicate_groups_view")
public class DuplicateGroupView implements Serializable {
/**
*
*/
private static final long serialVersionUID = 8387253981112795514L;
@Id
@Column(name = "id")
private String id;
@Column(name = "name")
private String name;
@Column(name = "city")
private String city;
@Column(name = "country")
private String country;
@Column(name = "n_duplicates")
private long numberOfDuplicates;
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 getCity() {
return city;
}
public void setCity(final String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(final String country) {
this.country = country;
}
public long getNumberOfDuplicates() {
return numberOfDuplicates;
}
public void setNumberOfDuplicates(final long numberOfDuplicates) {
this.numberOfDuplicates = numberOfDuplicates;
}
}

View File

@ -0,0 +1,109 @@
package eu.dnetlib.organizations.model.view;
import java.io.Serializable;
import java.time.OffsetDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "organizations_info_view")
public class OrganizationInfoView implements Serializable {
/**
*
*/
private static final long serialVersionUID = -6077637367923773598L;
@Id
@Column(name = "id")
private String id;
@Column(name = "name")
private String name;
@Column(name = "created_by")
private String createdBy;
@Column(name = "creation_date")
private OffsetDateTime creationDate;
@Column(name = "modified_by")
private String modifiedBy;
@Column(name = "modification_date")
private OffsetDateTime modificationDate;
@Column(name = "n_duplicates")
private long nDuplicates;
@Column(name = "n_conflicts")
private long nConflicts;
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 getCreatedBy() {
return createdBy;
}
public void setCreatedBy(final String createdBy) {
this.createdBy = createdBy;
}
public OffsetDateTime getCreationDate() {
return creationDate;
}
public void setCreationDate(final OffsetDateTime creationDate) {
this.creationDate = creationDate;
}
public String getModifiedBy() {
return modifiedBy;
}
public void setModifiedBy(final String modifiedBy) {
this.modifiedBy = modifiedBy;
}
public OffsetDateTime getModificationDate() {
return modificationDate;
}
public void setModificationDate(final OffsetDateTime modificationDate) {
this.modificationDate = modificationDate;
}
public long getnDuplicates() {
return nDuplicates;
}
public void setnDuplicates(final long nDuplicates) {
this.nDuplicates = nDuplicates;
}
public long getnConflicts() {
return nConflicts;
}
public void setnConflicts(final long nConflicts) {
this.nConflicts = nConflicts;
}
}

View File

@ -0,0 +1,131 @@
package eu.dnetlib.organizations.model.view;
import java.io.Serializable;
import java.util.Objects;
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.array.StringArrayType;
@Entity
@Table(name = "organizations_simple_view")
@TypeDefs({
@TypeDef(name = "string-array", typeClass = StringArrayType.class)
})
public class OrganizationSimpleView implements Serializable, Comparable<OrganizationSimpleView> {
/**
*
*/
private static final long serialVersionUID = 417248897119259446L;
@Id
@Column(name = "id")
private String id;
@Column(name = "name")
private String name;
@Column(name = "type")
private String type;
@Column(name = "city")
private String city;
@Column(name = "country")
private String country;
@Type(type = "string-array")
@Column(name = "acronyms", columnDefinition = "text[]")
private String[] acronyms;
public OrganizationSimpleView() {}
public OrganizationSimpleView(final String id) {
this.id = id;
}
public OrganizationSimpleView(final String id, final String name, final String type, final String city, final String country, final String[] acronyms) {
this.id = id;
this.name = name;
this.type = type;
this.city = city;
this.country = country;
this.acronyms = acronyms;
}
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 getType() {
return type;
}
public void setType(final String type) {
this.type = type;
}
public String getCity() {
return city;
}
public void setCity(final String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(final String country) {
this.country = country;
}
public String[] getAcronyms() {
return acronyms;
}
public void setAcronyms(final String[] acronyms) {
this.acronyms = acronyms;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (!(obj instanceof OrganizationSimpleView)) { return false; }
final OrganizationSimpleView other = (OrganizationSimpleView) obj;
return Objects.equals(id, other.id);
}
@Override
public int compareTo(final OrganizationSimpleView o) {
return id.compareTo(o.getId());
}
}

View File

@ -0,0 +1,169 @@
package eu.dnetlib.organizations.model.view;
import java.io.Serializable;
import java.util.Set;
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;
@Entity
@Table(name = "organizations_view")
@TypeDefs({
@TypeDef(name = "json", typeClass = JsonStringType.class),
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
})
public class OrganizationView implements Serializable {
/**
*
*/
private static final long serialVersionUID = -7864607073631041868L;
@Id
@Column(name = "id")
private String id;
@Column(name = "name")
private String name;
@Column(name = "type")
private String type;
@Column(name = "lat")
private Double lat;
@Column(name = "lng")
private Double lng;
@Column(name = "city")
private String city;
@Column(name = "country")
private String country;
@Type(type = "jsonb")
@Column(name = "other_ids", columnDefinition = "jsonb")
private Set<OtherIdentifier> otherIdentifiers;
@Type(type = "jsonb")
@Column(name = "other_names", columnDefinition = "jsonb")
private Set<OtherName> otherNames;
@Type(type = "jsonb")
@Column(name = "acronyms", columnDefinition = "jsonb")
private Set<String> acronyms;
@Type(type = "jsonb")
@Column(name = "urls", columnDefinition = "jsonb")
private Set<String> urls;
@Type(type = "jsonb")
@Column(name = "relations", columnDefinition = "jsonb")
private Set<RelationByOrg> relations;
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 getType() {
return type;
}
public void setType(final String type) {
this.type = type;
}
public Double getLat() {
return lat;
}
public void setLat(final Double lat) {
this.lat = lat;
}
public Double getLng() {
return lng;
}
public void setLng(final Double lng) {
this.lng = lng;
}
public String getCity() {
return city;
}
public void setCity(final String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(final String country) {
this.country = country;
}
public Set<OtherIdentifier> getOtherIdentifiers() {
return otherIdentifiers;
}
public void setOtherIdentifiers(final Set<OtherIdentifier> otherIdentifiers) {
this.otherIdentifiers = otherIdentifiers;
}
public Set<OtherName> getOtherNames() {
return otherNames;
}
public void setOtherNames(final Set<OtherName> otherNames) {
this.otherNames = otherNames;
}
public Set<String> getAcronyms() {
return acronyms;
}
public void setAcronyms(final Set<String> acronyms) {
this.acronyms = acronyms;
}
public Set<String> getUrls() {
return urls;
}
public void setUrls(final Set<String> urls) {
this.urls = urls;
}
public Set<RelationByOrg> getRelations() {
return relations;
}
public void setRelations(final Set<RelationByOrg> relations) {
this.relations = relations;
}
}

View File

@ -0,0 +1,46 @@
package eu.dnetlib.organizations.model.view;
import java.io.Serializable;
import java.util.Objects;
public class OtherIdentifier implements Serializable {
/**
*
*/
private static final long serialVersionUID = -8290245353090885241L;
private String id;
private String type;
public String getId() {
return id;
}
public void setId(final String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(final String type) {
this.type = type;
}
@Override
public int hashCode() {
return Objects.hash(id, type);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (!(obj instanceof OtherIdentifier)) { return false; }
final OtherIdentifier other = (OtherIdentifier) obj;
return Objects.equals(id, other.id) && Objects.equals(type, other.type);
}
}

View File

@ -0,0 +1,47 @@
package eu.dnetlib.organizations.model.view;
import java.io.Serializable;
import java.util.Objects;
public class OtherName implements Serializable {
/**
*
*/
private static final long serialVersionUID = -5212012138309835912L;
private String name;
private String lang;
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public String getLang() {
return lang;
}
public void setLang(final String lang) {
this.lang = lang;
}
@Override
public int hashCode() {
return Objects.hash(lang, name);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (!(obj instanceof OtherName)) { return false; }
final OtherName other = (OtherName) obj;
return Objects.equals(lang, other.lang) && Objects.equals(name, other.name);
}
}

View File

@ -0,0 +1,55 @@
package eu.dnetlib.organizations.model.view;
import java.io.Serializable;
import java.util.Objects;
public class RelationByOrg implements Serializable {
/**
*
*/
private static final long serialVersionUID = -659747760287666406L;
private String relatedOrgId;
private String relatedOrgName;
private String type;
public String getRelatedOrgId() {
return relatedOrgId;
}
public void setRelatedOrgId(final String relatedOrgId) {
this.relatedOrgId = relatedOrgId;
}
public String getRelatedOrgName() {
return relatedOrgName;
}
public void setRelatedOrgName(final String relatedOrgName) {
this.relatedOrgName = relatedOrgName;
}
public String getType() {
return type;
}
public void setType(final String type) {
this.type = type;
}
@Override
public int hashCode() {
return Objects.hash(relatedOrgId, relatedOrgName, type);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (!(obj instanceof RelationByOrg)) { return false; }
final RelationByOrg other = (RelationByOrg) obj;
return Objects.equals(relatedOrgId, other.relatedOrgId) && Objects.equals(relatedOrgName, other.relatedOrgName) && Objects.equals(type, other.type);
}
}

View File

@ -0,0 +1,53 @@
package eu.dnetlib.organizations.model.view;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "suggestions_info_by_country_view")
public class SuggestionInfoViewByCountry implements Serializable {
/**
*
*/
private static final long serialVersionUID = -6814272980951063075L;
@Id
@Column(name = "country")
private String country;
@Column(name = "n_duplicates")
private long nDuplicates;
@Column(name = "n_conflicts")
private long nConflicts;
public String getCountry() {
return country;
}
public void setCountry(final String country) {
this.country = country;
}
public long getnDuplicates() {
return nDuplicates;
}
public void setnDuplicates(final long nDuplicates) {
this.nDuplicates = nDuplicates;
}
public long getnConflicts() {
return nConflicts;
}
public void setnConflicts(final long nConflicts) {
this.nConflicts = nConflicts;
}
}

View File

@ -0,0 +1,73 @@
package eu.dnetlib.organizations.model.view;
import java.io.Serializable;
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.array.StringArrayType;
@Entity
@Table(name = "users_view")
@TypeDefs({
@TypeDef(name = "string-array", typeClass = StringArrayType.class)
})
public class UserView implements Serializable {
/**
*
*/
private static final long serialVersionUID = -3308680880727895075L;
@Id
@Column(name = "email")
private String email;
@Column(name = "valid")
private boolean valid;
@Column(name = "role")
private String role;
@Type(type = "string-array")
@Column(name = "countries", columnDefinition = "text[]")
private String[] countries;
public String getEmail() {
return email;
}
public void setEmail(final String email) {
this.email = email;
}
public boolean isValid() {
return valid;
}
public void setValid(final boolean valid) {
this.valid = valid;
}
public String getRole() {
return role;
}
public void setRole(final String role) {
this.role = role;
}
public String[] getCountries() {
return countries;
}
public void setCountries(final String[] countries) {
this.countries = countries;
}
}

View File

@ -0,0 +1,12 @@
package eu.dnetlib.organizations.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import eu.dnetlib.organizations.model.Acronym;
import eu.dnetlib.organizations.model.AcronymPK;
public interface AcronymRepository extends JpaRepository<Acronym, AcronymPK> {
void deleteByOrgId(String orgId);
}

View File

@ -0,0 +1,30 @@
package eu.dnetlib.organizations.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import eu.dnetlib.organizations.model.OpenaireConflict;
import eu.dnetlib.organizations.model.OpenaireConflictPK;
import eu.dnetlib.organizations.model.utils.OrganizationConflict;
@Repository
public interface OpenaireConflictRepository extends JpaRepository<OpenaireConflict, OpenaireConflictPK> {
@Query(value = "select o.id, o.name, o.type, o.city, o.country from oa_conflicts c left outer join organizations o on (c.id2 = o.id) where o.id is not null and c.id1 = ?", nativeQuery = true)
List<OrganizationConflict> getConflictsForId(String id);
Iterable<OpenaireConflict> findById1AndGroupIsNull(String id);
Iterable<OpenaireConflict> findById2AndGroupIsNull(String id);
@Modifying
@Query(value = "update oa_conflicts set idgroup = null", nativeQuery = true)
void resetGroupIds();
long countByGroupNull();
}

View File

@ -0,0 +1,15 @@
package eu.dnetlib.organizations.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import eu.dnetlib.organizations.model.OpenaireDuplicate;
import eu.dnetlib.organizations.model.OpenaireDuplicatePK;
@Repository
public interface OpenaireDuplicateRepository extends JpaRepository<OpenaireDuplicate, OpenaireDuplicatePK> {
List<OpenaireDuplicate> findByLocalId(String localId);
}

View File

@ -0,0 +1,21 @@
package eu.dnetlib.organizations.repository;
import java.time.OffsetDateTime;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import eu.dnetlib.organizations.model.Organization;
public interface OrganizationRepository extends JpaRepository<Organization, String> {
@Modifying
@Query("update Organization set created_by = ?2, creation_date = ?3 where id = ?1")
void updateCreationDate(String id, String user, OffsetDateTime now);
@Modifying
@Query("update Organization set modified_by = ?2, modification_date = ?3 where id = ?1")
void updateModificationDate(String id, String user, OffsetDateTime now);
}

View File

@ -0,0 +1,11 @@
package eu.dnetlib.organizations.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import eu.dnetlib.organizations.model.OtherIdentifier;
import eu.dnetlib.organizations.model.OtherIdentifierPK;
public interface OtherIdentifierRepository extends JpaRepository<OtherIdentifier, OtherIdentifierPK> {
void deleteByOrgId(String orgId);
}

View File

@ -0,0 +1,11 @@
package eu.dnetlib.organizations.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import eu.dnetlib.organizations.model.OtherName;
import eu.dnetlib.organizations.model.OtherNamePK;
public interface OtherNameRepository extends JpaRepository<OtherName, OtherNamePK> {
void deleteByOrgId(String orgId);
}

View File

@ -0,0 +1,13 @@
package eu.dnetlib.organizations.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import eu.dnetlib.organizations.model.Relationship;
import eu.dnetlib.organizations.model.RelationshipPK;
public interface RelationshipRepository extends JpaRepository<Relationship, RelationshipPK> {
void deleteById1(String id1);
void deleteById2(String id2);
}

View File

@ -0,0 +1,11 @@
package eu.dnetlib.organizations.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import eu.dnetlib.organizations.model.Url;
import eu.dnetlib.organizations.model.UrlPK;
public interface UrlRepository extends JpaRepository<Url, UrlPK> {
void deleteByOrgId(String orgId);
}

View File

@ -0,0 +1,24 @@
package eu.dnetlib.organizations.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import eu.dnetlib.organizations.model.UserCountry;
import eu.dnetlib.organizations.model.UserCountryPK;
public interface UserCountryRepository extends JpaRepository<UserCountry, UserCountryPK> {
void deleteByEmail(String email);
@Query(value = "select country from user_countries where email = ?1", nativeQuery = true)
List<String> getCountriesForUser(String email);
@Query(value = "select count(o.country) > 0 from organizations o left outer join user_countries uc on (o.country = uc.country) where o.id = ?1 and uc.email = ?2", nativeQuery = true)
boolean verifyAuthorizationForId(String id, String user);
@Query(value = "select count(country) > 0 from user_countries where country = ?1 and email = ?2", nativeQuery = true)
boolean verifyAuthorizationForCountry(String country, String user);
}

View File

@ -0,0 +1,9 @@
package eu.dnetlib.organizations.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import eu.dnetlib.organizations.model.User;
public interface UserRepository extends JpaRepository<User, String> {
}

View File

@ -0,0 +1,15 @@
package eu.dnetlib.organizations.repository.readonly;
import java.util.List;
import org.springframework.stereotype.Repository;
import eu.dnetlib.organizations.model.view.ConflictGroupView;
import eu.dnetlib.organizations.model.view.ConflictGroupViewPK;
@Repository
public interface ConflictGroupViewRepository extends ReadOnlyRepository<ConflictGroupView, ConflictGroupViewPK> {
List<ConflictGroupView> findByCountry1OrCountry2(String country1, String country2);
}

View File

@ -0,0 +1,13 @@
package eu.dnetlib.organizations.repository.readonly;
import java.util.List;
import org.springframework.stereotype.Repository;
import eu.dnetlib.organizations.model.view.DuplicateGroupView;
@Repository
public interface DuplicateGroupViewRepository extends ReadOnlyRepository<DuplicateGroupView, String> {
List<DuplicateGroupView> findByCountry(String country);
}

View File

@ -0,0 +1,10 @@
package eu.dnetlib.organizations.repository.readonly;
import org.springframework.stereotype.Repository;
import eu.dnetlib.organizations.model.view.OrganizationInfoView;
@Repository
public interface OrganizationInfoViewRepository extends ReadOnlyRepository<OrganizationInfoView, String> {
}

View File

@ -0,0 +1,51 @@
package eu.dnetlib.organizations.repository.readonly;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import eu.dnetlib.organizations.model.utils.BrowseEntry;
import eu.dnetlib.organizations.model.view.OrganizationSimpleView;
@Repository
public interface OrganizationSimpleViewRepository extends ReadOnlyRepository<OrganizationSimpleView, String> {
// SEARCH
Page<OrganizationSimpleView> findByNameContainingIgnoreCase(String name, Pageable pageable);
// SEARCH FOR USER
@Query(value = "select o.* from organizations_simple_view o left outer join user_countries uc on (uc.country = o.country) where o.name ilike %:text% and uc.email = :email", nativeQuery = true)
Page<OrganizationSimpleView> findByNameForUser(String text, String email, Pageable pageable);
// BROWSE BY COUNTRY
@Query(value = "select country as value, count(*) as count from organizations group by country order by count desc", nativeQuery = true)
List<BrowseEntry> browseCountries();
// BROWSE BY COUNTRY FOR USER
@Query(value = "select o.country as value, count(o.country) as count from user_countries uc left outer join organizations o on (uc.country = o.country) where uc.email=?1 group by o.country order by count desc", nativeQuery = true)
List<BrowseEntry> browseCountriesForUser(String email);
Page<OrganizationSimpleView> findByCountry(String country, Pageable pageable);
// BROWSE BY ORG TYPE
@Query(value = "select type as value, count(*) as count from organizations group by type order by count desc", nativeQuery = true)
List<BrowseEntry> browseTypes();
// BROWSE BY ORG TYPE FOR USER
@Query(value = "select o.type as value, count(o.type) as count "
+ "from organizations o "
+ "left outer join user_countries uc on (uc.country = o.country) "
+ "where uc.email=?1 "
+ "group by o.type "
+ "order by count desc;", nativeQuery = true)
List<BrowseEntry> browseTypesForUser(String email);
Page<OrganizationSimpleView> findByType(String type, Pageable pageable);
@Query(value = "select o.* from organizations_simple_view o left outer join user_countries uc on (uc.country = o.country) where uc.email = ?2 and o.type = ?1", nativeQuery = true)
Page<OrganizationSimpleView> findByTypeForUser(String type, String name, Pageable pageable);
}

View File

@ -0,0 +1,10 @@
package eu.dnetlib.organizations.repository.readonly;
import org.springframework.stereotype.Repository;
import eu.dnetlib.organizations.model.view.OrganizationView;
@Repository
public interface OrganizationViewRepository extends ReadOnlyRepository<OrganizationView, String> {
}

View File

@ -0,0 +1,22 @@
package eu.dnetlib.organizations.repository.readonly;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.Repository;
@NoRepositoryBean
public interface ReadOnlyRepository<T, ID> extends Repository<T, ID> {
Optional<T> findById(ID id);
boolean existsById(ID id);
Page<T> findAll(Pageable pageable);
Iterable<T> findAll();
long count();
}

View File

@ -0,0 +1,10 @@
package eu.dnetlib.organizations.repository.readonly;
import org.springframework.stereotype.Repository;
import eu.dnetlib.organizations.model.view.SuggestionInfoViewByCountry;
@Repository
public interface SuggestionInfoViewByCountryRepository extends ReadOnlyRepository<SuggestionInfoViewByCountry, String> {
}

View File

@ -0,0 +1,10 @@
package eu.dnetlib.organizations.repository.readonly;
import org.springframework.stereotype.Repository;
import eu.dnetlib.organizations.model.view.UserView;
@Repository
public interface UserViewRepository extends ReadOnlyRepository<UserView, String> {
}

View File

@ -0,0 +1,239 @@
package eu.dnetlib.organizations.utils;
import java.time.OffsetDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import eu.dnetlib.organizations.controller.UserRole;
import eu.dnetlib.organizations.model.Acronym;
import eu.dnetlib.organizations.model.OpenaireConflict;
import eu.dnetlib.organizations.model.OpenaireConflictPK;
import eu.dnetlib.organizations.model.Organization;
import eu.dnetlib.organizations.model.OtherIdentifier;
import eu.dnetlib.organizations.model.OtherName;
import eu.dnetlib.organizations.model.Relationship;
import eu.dnetlib.organizations.model.Url;
import eu.dnetlib.organizations.model.User;
import eu.dnetlib.organizations.model.UserCountry;
import eu.dnetlib.organizations.model.view.OrganizationView;
import eu.dnetlib.organizations.model.view.UserView;
import eu.dnetlib.organizations.repository.AcronymRepository;
import eu.dnetlib.organizations.repository.OpenaireConflictRepository;
import eu.dnetlib.organizations.repository.OrganizationRepository;
import eu.dnetlib.organizations.repository.OtherIdentifierRepository;
import eu.dnetlib.organizations.repository.OtherNameRepository;
import eu.dnetlib.organizations.repository.RelationshipRepository;
import eu.dnetlib.organizations.repository.UrlRepository;
import eu.dnetlib.organizations.repository.UserCountryRepository;
import eu.dnetlib.organizations.repository.UserRepository;
@Component
public class DatabaseUtils {
@Autowired
private AcronymRepository acronymRepository;
@Autowired
private OrganizationRepository organizationRepository;
@Autowired
private OtherIdentifierRepository otherIdentifierRepository;
@Autowired
private OtherNameRepository otherNameRepository;
@Autowired
private UrlRepository urlRepository;
@Autowired
private RelationshipRepository relationshipRepository;
@Autowired
private UserRepository userRepository;
@Autowired
private UserCountryRepository userCountryRepository;
@Autowired
private OpenaireConflictRepository openaireConflictRepository;
@Autowired
private JdbcTemplate jdbcTemplate;
public enum VocabularyTable {
languages, countries, org_types, id_types, rel_types, simrel_types
}
@Transactional
public String insertOrUpdateOrganization(final OrganizationView orgView, final String user, final boolean update) {
if (update) {
cleanOldRelations(orgView.getId());
}
final Organization org = new Organization(update ? orgView.getId() : null,
orgView.getName(),
orgView.getType(),
orgView.getLat(), orgView.getLng(),
orgView.getCity(), orgView.getCountry());
final String orgId = organizationRepository.save(org).getId();
makeNewRelations(orgView, orgId);
updateHistoryFields(orgId, user, update);
return orgId;
}
private void updateHistoryFields(final String id, final String user, final boolean update) {
final OffsetDateTime now = OffsetDateTime.now();
if (update) {
organizationRepository.updateModificationDate(id, user, now);
} else {
organizationRepository.updateCreationDate(id, user, now);
organizationRepository.updateModificationDate(id, user, now);
}
}
private void makeNewRelations(final OrganizationView orgView, final String orgId) {
orgView.getAcronyms().forEach(s -> acronymRepository.save(new Acronym(orgId, s)));
orgView.getOtherNames().forEach(n -> otherNameRepository.save(new OtherName(orgId, n.getName(), n.getLang())));
orgView.getOtherIdentifiers().forEach(id -> otherIdentifierRepository.save(new OtherIdentifier(orgId, id.getId(), id.getType())));
orgView.getUrls().forEach(u -> urlRepository.save(new Url(orgId, u)));
orgView.getRelations().forEach(r -> makeRelation(orgId, r.getRelatedOrgId(), RelationType.valueOf(r.getType())));
}
private void cleanOldRelations(final String id) {
acronymRepository.deleteByOrgId(id);
otherNameRepository.deleteByOrgId(id);
otherIdentifierRepository.deleteByOrgId(id);
urlRepository.deleteByOrgId(id);
relationshipRepository.deleteById1(id);
relationshipRepository.deleteById2(id);
}
@Cacheable("vocs")
public List<String> listValuesOfVocabularyTable(final VocabularyTable table) {
return jdbcTemplate.queryForList("select val from " + table, String.class);
}
@Cacheable("countries_for_user")
public List<String> listCountriesForUser(final String name) {
return jdbcTemplate.queryForList("select country from user_countries where email = ?", String.class, name);
}
@Transactional
public void saveUser(@RequestBody final UserView userView) {
final User user = userRepository.findById(userView.getEmail()).orElseThrow(() -> new RuntimeException("User not found"));
user.setRole(userView.getRole());
user.setValid(userView.isValid());
userRepository.save(user);
userCountryRepository.deleteByEmail(userView.getEmail());
if (userView.getCountries() != null) {
userCountryRepository
.saveAll(Arrays.stream(userView.getCountries()).map(c -> new UserCountry(userView.getEmail(), c)).collect(Collectors.toList()));
}
}
@Transactional
public void deleteUser(final String email) {
userCountryRepository.deleteByEmail(email);
userRepository.deleteById(email);
}
@Transactional
public void newUser(final String email, final List<String> countries) {
final User user = new User();
user.setEmail(email);
user.setRole(UserRole.PENDING.name());
user.setValid(false);
userRepository.save(user);
if (countries != null) {
userCountryRepository.saveAll(countries.stream().map(c -> new UserCountry(email, c)).collect(Collectors.toList()));
}
}
@Transactional
public void verifyConflictGroups(final boolean forceUpdate) {
if (forceUpdate || openaireConflictRepository.countByGroupNull() > 0) {
openaireConflictRepository.resetGroupIds();
final Map<String, Set<String>> groups = new HashMap<>();
for (final OpenaireConflict w : openaireConflictRepository.findAll()) {
final List<String> list = findExistingGroupsForRel(w, groups);
if (list.isEmpty()) {
final String idGroup = generateGroupId();
groups.put(idGroup, new HashSet<>());
addToGroup(groups, idGroup, w);
} else if (list.size() == 1) {
addToGroup(groups, list.get(0), w);
} else {
final String idGroup = generateGroupId();
groups.put(idGroup, new TreeSet<>());
list.forEach(id -> groups.get(idGroup).addAll(groups.get(id)));
list.forEach(id -> groups.remove(id));
addToGroup(groups, idGroup, w);
}
}
for (final Entry<String, Set<String>> e : groups.entrySet()) {
final String gid = e.getKey();
for (final String orgId : e.getValue()) {
for (final OpenaireConflict oc : openaireConflictRepository.findById1AndGroupIsNull(orgId)) {
oc.setGroup(gid);
openaireConflictRepository.save(oc);
}
for (final OpenaireConflict oc : openaireConflictRepository.findById2AndGroupIsNull(orgId)) {
oc.setGroup(gid);
openaireConflictRepository.save(oc);
}
}
}
}
}
private String generateGroupId() {
return "group::" + UUID.randomUUID();
}
private List<String> findExistingGroupsForRel(final OpenaireConflict w, final Map<String, Set<String>> groups) {
return groups.entrySet()
.stream()
.filter(e -> {
return e.getValue().contains(w.getId1()) || e.getValue().contains(w.getId2());
})
.map(e -> e.getKey())
.distinct()
.collect(Collectors.toList());
}
private void addToGroup(final Map<String, Set<String>> groups, final String gid, final OpenaireConflict w) {
groups.get(gid).add(w.getId1());
groups.get(gid).add(w.getId2());
}
@Transactional
public List<Relationship> makeRelation(final String id1, final String id2, final RelationType type) {
final Relationship r1 = new Relationship(id1, id2, type.toString());
final Relationship r2 = new Relationship(id2, id1, type.getInverse().toString());
relationshipRepository.save(r1);
relationshipRepository.save(r2);
if (type == RelationType.Merged_In || type == RelationType.Merges) {
openaireConflictRepository.findById(new OpenaireConflictPK(id1, id2)).ifPresent(openaireConflictRepository::delete);
openaireConflictRepository.findById(new OpenaireConflictPK(id2, id1)).ifPresent(openaireConflictRepository::delete);
}
return Arrays.asList(r1, r2);
}
}

View File

@ -0,0 +1,8 @@
package eu.dnetlib.organizations.utils;
public class OpenOrgsConstants {
public static final String OPENORGS_PREFIX = "openorgs____::";
public static final String OPENORGS_MESH_PREFIX = "openorgsmesh::";
}

View File

@ -0,0 +1,23 @@
package eu.dnetlib.organizations.utils;
public enum RelationType {
Child, Parent, Related, Other, Merged_In, Merges;
public RelationType getInverse() {
switch (this) {
case Child:
return Parent;
case Parent:
return Child;
case Related:
return Related;
case Merged_In:
return Merges;
case Merges:
return Merged_In;
default:
return Other;
}
}
}

View File

@ -0,0 +1,5 @@
package eu.dnetlib.organizations.utils;
public enum SimilarityType {
suggested, is_similar, is_different
}

View File

@ -0,0 +1,18 @@
spring.main.banner-mode = off
logging.level.root = INFO
spring.datasource.url=jdbc:postgresql://localhost:5432/dnet_orgs
spring.datasource.username=
spring.datasource.password=
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = validate
spring.jpa.properties.hibernate.hbm2dll.extra_physical_table_types = MATERIALIZED VIEW
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.open-in-view=true
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.properties.hibernate.format_sql=true

View File

@ -0,0 +1,27 @@
CREATE TEMPORARY TABLE tmp_simrels (
local_id text NOT NULL,
oa_original_id text NOT NULL,
oa_name text NOT NULL,
oa_acronym text,
oa_country text,
oa_url text,
oa_collectedfrom text
);
COPY tmp_simrels(local_id, oa_original_id, oa_name, oa_acronym, oa_country, oa_url, oa_collectedfrom) FROM '/Users/michele/Develop/dnet45/dnet-orgs-database-application/src/main/resources/tmp_data/rels.part-r-00000.tsv' DELIMITER E'\t';;
DELETE FROM oa_duplicates WHERE reltype = 'suggested';
DELETE FROM oa_conflicts WHERE reltype = 'suggested';
UPDATE oa_conflicts SET idgroup = NULL;
INSERT INTO oa_duplicates (local_id, oa_original_id, oa_name, oa_acronym, oa_country, oa_url, oa_collectedfrom)
SELECT local_id, oa_original_id, oa_name, oa_acronym, oa_country, oa_url, oa_collectedfrom
FROM tmp_simrels
WHERE oa_original_id NOT LIKE 'openorgs____::%'
ON CONFLICT DO NOTHING;
INSERT INTO oa_conflicts (id1, id2)
SELECT local_id, oa_original_id
FROM tmp_simrels
WHERE oa_original_id LIKE 'openorgs____::%'
ON CONFLICT DO NOTHING;

View File

@ -0,0 +1,103 @@
CREATE TEMP TABLE grid_institutes (
grid_id text,
name text,
wikipedia_url text,
email_address text,
established int
);
CREATE TEMP TABLE grid_geonames (
geonames_city_id text,
city text,
nuts_level1_code text,
nuts_level1_name text,
nuts_level2_code text,
nuts_level2_name text,
nuts_level3_code text,
nuts_level3_name text,
geonames_admin1_code text,
geonames_admin1_name text,
geonames_admin1_ascii_name text,
geonames_admin2_code text,
geonames_admin2_name text,
geonames_admin2_ascii_name text
);
CREATE TEMP TABLE grid_addresses (
grid_id text,
line_1 text,
line_2 text,
line_3 text,
lat double precision,
lng double precision,
postcode text,
is_primary boolean,
city text,
state text,
state_code text,
country text,
country_code text,
geonames_city_id int
);
CREATE TEMP TABLE grid_external_ids (
grid_id text,
external_id_type text,
external_id text
);
CREATE TEMP TABLE grid_labels (
grid_id text,
iso639 text,
label text
);
CREATE TEMP TABLE grid_relationships (
grid_id text,
relationship_type text,
related_grid_id text
);
CREATE TEMP TABLE grid_types (
grid_id text,
type text
);
CREATE TEMP TABLE grid_links (
grid_id text,
link text
);
CREATE TEMP TABLE grid_acronyms (
grid_id text,
acronym text
);
CREATE TEMP TABLE grid_aliases (
grid_id text,
alias text
);
COPY grid_institutes (grid_id,name,wikipedia_url,email_address,established) FROM '/Users/michele/Develop/dnet45/dnet-orgs-database-application/data/grid-2019-05-06/full_tables/institutes.csv' CSV HEADER;
COPY grid_geonames (geonames_city_id,city,nuts_level1_code,nuts_level1_name,nuts_level2_code,nuts_level2_name,nuts_level3_code,nuts_level3_name,geonames_admin1_code,geonames_admin1_name,geonames_admin1_ascii_name,geonames_admin2_code,geonames_admin2_name,geonames_admin2_ascii_name) FROM '/Users/michele/Develop/dnet45/dnet-orgs-database-application/data/grid-2019-05-06/full_tables/geonames.csv' CSV HEADER;
COPY grid_addresses (grid_id,line_1,line_2,line_3,lat,lng,postcode,is_primary,city,state,state_code,country,country_code,geonames_city_id) FROM '/Users/michele/Develop/dnet45/dnet-orgs-database-application/data/grid-2019-05-06/full_tables/addresses.csv' CSV HEADER;
COPY grid_external_ids (grid_id,external_id_type,external_id) FROM '/Users/michele/Develop/dnet45/dnet-orgs-database-application/data/grid-2019-05-06/full_tables/external_ids.csv' CSV HEADER;
COPY grid_labels (grid_id,iso639,label) FROM '/Users/michele/Develop/dnet45/dnet-orgs-database-application/data/grid-2019-05-06/full_tables/labels.csv' CSV HEADER;
COPY grid_relationships (grid_id,relationship_type,related_grid_id) FROM '/Users/michele/Develop/dnet45/dnet-orgs-database-application/data/grid-2019-05-06/full_tables/relationships.csv' CSV HEADER;
COPY grid_types (grid_id,type) FROM '/Users/michele/Develop/dnet45/dnet-orgs-database-application/data/grid-2019-05-06/full_tables/types.csv' CSV HEADER;
COPY grid_links (grid_id,link) FROM '/Users/michele/Develop/dnet45/dnet-orgs-database-application/data/grid-2019-05-06/full_tables/links.csv' CSV HEADER;
COPY grid_acronyms (grid_id,acronym) FROM '/Users/michele/Develop/dnet45/dnet-orgs-database-application/data/grid-2019-05-06/full_tables/acronyms.csv' CSV HEADER;
COPY grid_aliases (grid_id,alias) FROM '/Users/michele/Develop/dnet45/dnet-orgs-database-application/data/grid-2019-05-06/full_tables/aliases.csv' CSV HEADER;
INSERT INTO organizations(id, name, type, lat, lng, city, country, created_by, modified_by) (SELECT 'tmp::'||md5(o.grid_id), o.name, COALESCE(t.type, 'UNKNOWN'), a.lat, a.lng, a.city, a.country_code, 'import:grid.ac', 'import:grid.ac' FROM grid_institutes o LEFT OUTER JOIN grid_addresses a ON (o.grid_id=a.grid_id) LEFT OUTER JOIN grid_types t ON (o.grid_id=t.grid_id)) ON CONFLICT DO NOTHING;
INSERT INTO other_ids (id, otherid, type) (SELECT 'tmp::'||md5(grid_id), grid_id, 'grid.ac' FROM grid_institutes ) ON CONFLICT DO NOTHING;
INSERT INTO other_ids (id, otherid, type) (SELECT 'tmp::'||md5(grid_id), external_id, external_id_type FROM grid_external_ids ) ON CONFLICT DO NOTHING;
INSERT INTO other_names (id, lang, name) (SELECT 'tmp::'||md5(grid_id), 'en', name FROM grid_institutes ) ON CONFLICT DO NOTHING;
INSERT INTO other_names (id, lang, name) (SELECT 'tmp::'||md5(grid_id), iso639, label FROM grid_labels ) ON CONFLICT DO NOTHING;
INSERT INTO other_names (id, lang, name) (SELECT 'tmp::'||md5(grid_id), 'UNKNOWN', alias FROM grid_aliases ) ON CONFLICT DO NOTHING;
INSERT INTO acronyms (id, acronym) (SELECT 'tmp::'||md5(grid_id), acronym FROM grid_acronyms ) ON CONFLICT DO NOTHING;
INSERT INTO relationships(id1, reltype, id2) (SELECT 'tmp::'||md5(grid_id), relationship_type, 'tmp::'||md5(related_grid_id) FROM grid_relationships) ON CONFLICT DO NOTHING;
INSERT INTO urls (id, url) (SELECT 'tmp::'||md5(grid_id), link FROM grid_links ) ON CONFLICT DO NOTHING;
update organizations set id = DEFAULT;

View File

@ -0,0 +1 @@
INSERT INTO users(email, valid, role) VALUES ('michele.artini@isti.cnr.it', true, 'ADMIN');

View File

@ -0,0 +1,262 @@
DROP VIEW organizations_view;
DROP VIEW organizations_info_view;
DROP VIEW organizations_simple_view;
DROP VIEW users_view;
DROP VIEW conflict_groups_view;
DROP VIEW suggestions_info_by_country_view;
DROP VIEW duplicate_groups_view
DROP TABLE IF EXISTS other_ids;
DROP TABLE IF EXISTS other_names;
DROP TABLE IF EXISTS acronyms;
DROP TABLE IF EXISTS relationships;
DROP TABLE IF EXISTS urls;
DROP TABLE IF EXISTS oa_duplicates;
DROP TABLE IF EXISTS oa_conflicts;
DROP TABLE IF EXISTS organizations;
DROP TABLE IF EXISTS org_types;
DROP TABLE IF EXISTS user_countries;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS user_roles;
DROP TABLE IF EXISTS countries;
DROP TABLE IF EXISTS id_types;
DROP TABLE IF EXISTS languages;
DROP SEQUENCE IF EXISTS organizations_id_seq;
CREATE TABLE org_types (val text PRIMARY KEY);
INSERT INTO org_types VALUES ('Archive'), ('Company'), ('Education'), ('Facility'), ('Government'), ('Healthcare'), ('Nonprofit'), ('Other'), ('UNKNOWN');
CREATE TABLE id_types (val text PRIMARY KEY);
INSERT INTO id_types VALUES ('CNRS'), ('FundRef'), ('HESA'), ('ISNI'), ('OrgRef'), ('UCAS'), ('UKPRN'), ('Wikidata'), ('grid.ac');
CREATE TABLE languages (val text PRIMARY KEY);
INSERT INTO languages VALUES ('UNKNOWN'), ('aa'), ('af'), ('am'), ('ar'), ('as'), ('az'), ('ba'), ('be'), ('bg'), ('bn'), ('br'), ('bs'), ('ca'), ('ch'), ('co'), ('cs'), ('cy'), ('da'),
('de'), ('dv'), ('dz'), ('el'), ('en'), ('eo'), ('es'), ('et'), ('eu'), ('fa'), ('fi'), ('fr'), ('fy'), ('ga'), ('gd'), ('gl'), ('gu'), ('he'), ('hi'), ('hr'), ('hu'), ('hy'), ('id'), ('is'),
('it'), ('iu'), ('ja'), ('jv'), ('ka'), ('kk'), ('kl'), ('km'), ('kn'), ('ko'), ('ku'), ('ky'), ('la'), ('lb'), ('lo'), ('lt'), ('lv'), ('mg'), ('mi'), ('mk'), ('ml'), ('mn'), ('mr'), ('ms'),
('mt'), ('my'), ('nb'), ('ne'), ('nl'), ('nn'), ('no'), ('oc'), ('or'), ('pa'), ('pl'), ('ps'), ('pt'), ('rm'), ('ro'), ('ru'), ('rw'), ('sa'), ('sd'), ('si'), ('sk'), ('sl'), ('so'), ('sq'),
('sr'), ('sv'), ('sw'), ('ta'), ('te'), ('tg'), ('th'), ('tk'), ('tl'), ('tr'), ('tt'), ('ug'), ('uk'), ('ur'), ('uz'), ('vi'), ('xh'), ('yo'), ('zh'), ('zu');
CREATE TABLE countries (val text PRIMARY KEY);
INSERT INTO countries VALUES ('UNKNOWN'), ('AD'), ('AE'), ('AF'), ('AG'), ('AL'), ('AM'), ('AO'), ('AR'), ('AT'), ('AU'), ('AW'), ('AX'), ('AZ'), ('BA'), ('BB'), ('BD'), ('BE'), ('BF'), ('BG'), ('BH'), ('BI'),
('BJ'), ('BM'), ('BN'), ('BO'), ('BQ'), ('BR'), ('BS'), ('BT'), ('BW'), ('BY'), ('BZ'), ('CA'), ('CD'), ('CF'), ('CG'), ('CH'), ('CI'), ('CL'), ('CM'), ('CN'), ('CO'), ('CR'),
('CU'), ('CV'), ('CW'), ('CY'), ('CZ'), ('DE'), ('DJ'), ('DK'), ('DM'), ('DO'), ('DZ'), ('EC'), ('EE'), ('EG'), ('EH'), ('ER'), ('ES'), ('ET'), ('FI'), ('FJ'), ('FM'), ('FO'),
('FR'), ('GA'), ('GB'), ('GD'), ('GE'), ('GF'), ('GH'), ('GI'), ('GL'), ('GM'), ('GN'), ('GP'), ('GQ'), ('GR'), ('GT'), ('GW'), ('GY'), ('HN'), ('HR'), ('HT'), ('HU'), ('ID'),
('IE'), ('IL'), ('IM'), ('IN'), ('IQ'), ('IR'), ('IS'), ('IT'), ('JE'), ('JM'), ('JO'), ('JP'), ('KE'), ('KG'), ('KH'), ('KN'), ('KP'), ('KR'), ('KW'), ('KY'), ('KZ'), ('LA'),
('LB'), ('LC'), ('LI'), ('LK'), ('LR'), ('LS'), ('LT'), ('LU'), ('LV'), ('LY'), ('MA'), ('MC'), ('MD'), ('ME'), ('MG'), ('MK'), ('ML'), ('MM'), ('MN'), ('MO'), ('MQ'), ('MR'),
('MS'), ('MT'), ('MU'), ('MV'), ('MW'), ('MX'), ('MY'), ('MZ'), ('NA'), ('NC'), ('NE'), ('NG'), ('NI'), ('NL'), ('NO'), ('NP'), ('NU'), ('NZ'), ('OM'), ('PA'), ('PE'), ('PF'),
('PG'), ('PH'), ('PK'), ('PL'), ('PS'), ('PT'), ('PW'), ('PY'), ('QA'), ('RE'), ('RO'), ('RS'), ('RU'), ('RW'), ('SA'), ('SB'), ('SC'), ('SD'), ('SE'), ('SG'), ('SI'), ('SJ'),
('SK'), ('SL'), ('SM'), ('SN'), ('SO'), ('SR'), ('SS'), ('ST'), ('SV'), ('SX'), ('SY'), ('SZ'), ('TC'), ('TD'), ('TG'), ('TH'), ('TJ'), ('TL'), ('TM'), ('TN'), ('TO'), ('TR'),
('TT'), ('TV'), ('TW'), ('TZ'), ('UA'), ('UG'), ('US'), ('UY'), ('UZ'), ('VA'), ('VC'), ('VE'), ('VG'), ('VN'), ('WS'), ('XK'), ('YE'), ('ZA'), ('ZM'), ('ZW');
CREATE TABLE user_roles(role text PRIMARY KEY);
INSERT INTO user_roles VALUES ('ADMIN'), ('NATIONAL_ADMIN'), ('USER'), ('PENDING'), ('NOT_AUTHORIZED');
CREATE TABLE users (
email text PRIMARY KEY,
valid boolean DEFAULT true,
role text NOT NULL default 'USER' REFERENCES user_roles(role)
);
CREATE TABLE user_countries (
email text REFERENCES users(email),
country text REFERENCES countries(val),
PRIMARY KEY(email, country)
);
CREATE SEQUENCE organizations_id_seq;
CREATE TABLE organizations (
id text PRIMARY KEY DEFAULT 'openorgs____::'||lpad(nextval('organizations_id_seq')::text,10,'0'),
name text,
type text NOT NULL DEFAULT 'UNKNOWN' REFERENCES org_types(val),
lat double precision,
lng double precision,
city text,
country text REFERENCES countries(val),
created_by text,
creation_date timestamp with time zone DEFAULT now(),
modified_by text,
modification_date timestamp with time zone DEFAULT now()
);
CREATE INDEX organizations_type_idx ON organizations(type);
CREATE INDEX organizations_country_idx ON organizations(country);
CREATE TABLE other_ids (
id text REFERENCES organizations(id) ON UPDATE CASCADE,
otherid text,
type text REFERENCES id_types(val),
PRIMARY KEY (id, otherid, type)
);
CREATE INDEX other_ids_id_idx ON other_ids(id);
CREATE TABLE other_names (
id text REFERENCES organizations(id) ON UPDATE CASCADE,
name text,
lang text REFERENCES languages(val),
PRIMARY KEY (id, name, lang)
);
CREATE INDEX other_names_id_idx ON other_names(id);
CREATE TABLE acronyms (
id text REFERENCES organizations(id) ON UPDATE CASCADE,
acronym text,
PRIMARY KEY (id, acronym)
);
CREATE INDEX acronyms_id_idx ON acronyms(id);
CREATE TABLE relationships (
id1 text REFERENCES organizations(id) ON UPDATE CASCADE,
reltype text,
id2 text REFERENCES organizations(id) ON UPDATE CASCADE,
PRIMARY KEY (id1, reltype, id2)
);
CREATE INDEX relationships_id1_idx ON relationships(id1);
CREATE INDEX relationships_id2_idx ON relationships(id2);
CREATE TABLE urls (
id text REFERENCES organizations(id) ON UPDATE CASCADE,
url text,
PRIMARY KEY (id, url)
);
CREATE INDEX urls_id_idx ON urls(id);
CREATE TABLE oa_duplicates (
local_id text REFERENCES organizations(id) ON UPDATE CASCADE,
oa_original_id text NOT NULL,
oa_name text NOT NULL,
oa_acronym text,
oa_country text,
oa_url text,
oa_collectedfrom text,
reltype text NOT NULL DEFAULT 'suggested',
PRIMARY KEY (local_id, oa_original_id)
);
CREATE INDEX oa_duplicates_local_id_idx ON oa_duplicates(local_id);
CREATE TABLE oa_conflicts (
id1 text REFERENCES organizations(id) ON UPDATE CASCADE,
id2 text REFERENCES organizations(id) ON UPDATE CASCADE,
reltype text NOT NULL DEFAULT 'suggested',
idgroup text,
PRIMARY KEY (id1, id2)
);
CREATE INDEX oa_conflicts_id1_idx ON oa_conflicts(id1);
CREATE INDEX oa_conflicts_id2_idx ON oa_conflicts(id2);
CREATE INDEX oa_conflicts_idgroup_idx ON oa_conflicts(idgroup);
CREATE VIEW organizations_view AS SELECT
org.id,
org.name,
org.type,
org.lat,
org.lng,
org.city,
org.country,
COALESCE(jsonb_agg(DISTINCT jsonb_build_object('id', oid.otherid, 'type', oid.type)) FILTER (WHERE oid.otherid IS NOT NULL), '[]') AS other_ids,
COALESCE(jsonb_agg(DISTINCT jsonb_build_object('name', n.name, 'lang', n.lang)) FILTER (WHERE n.name IS NOT NULL), '[]') AS other_names,
COALESCE(jsonb_agg(DISTINCT a.acronym) FILTER (WHERE a.acronym IS NOT NULL), '[]') AS acronyms,
COALESCE(jsonb_agg(DISTINCT u.url) FILTER (WHERE u.url IS NOT NULL), '[]') AS urls,
COALESCE(jsonb_agg(DISTINCT jsonb_build_object('relatedOrgId', relorg.id, 'relatedOrgName', relorg.name, 'type', r.reltype)) FILTER (WHERE relorg.id IS NOT NULL), '[]') AS relations
FROM
organizations org
LEFT OUTER JOIN other_ids oid ON (org.id = oid.id)
LEFT OUTER JOIN other_names n ON (org.id = n.id)
LEFT OUTER JOIN acronyms a ON (org.id = a.id)
LEFT OUTER JOIN urls u ON (org.id = u.id)
LEFT OUTER JOIN relationships r ON (org.id = r.id1)
LEFT OUTER JOIN organizations relorg ON (relorg.id = r.id2)
GROUP BY
org.id,
org.name,
org.type,
org.lat,
org.lng,
org.city,
org.country;
CREATE VIEW organizations_info_view AS SELECT
org.id,
org.name,
org.created_by,
org.creation_date,
org.modified_by,
org.modification_date,
count(DISTINCT d.oa_original_id) as n_duplicates,
count(DISTINCT c.id2) as n_conflicts
FROM organizations org
LEFT OUTER JOIN oa_duplicates d ON (org.id = d.local_id AND d.reltype = 'suggested')
LEFT OUTER JOIN oa_conflicts c ON (org.id = c.id1 AND c.reltype = 'suggested')
GROUP BY org.id;
CREATE VIEW organizations_simple_view AS SELECT
org.id,
org.name,
org.type,
org.city,
org.country,
array_agg(DISTINCT a.acronym) FILTER (WHERE a.acronym IS NOT NULL) AS acronyms
FROM
organizations org
LEFT OUTER JOIN acronyms a ON (org.id = a.id)
GROUP BY
org.id,
org.name,
org.type,
org.city,
org.country;
CREATE VIEW users_view AS SELECT
u.email,
u.valid,
u.role,
array_agg(uc.country) FILTER (WHERE uc.country IS NOT NULL) AS countries
FROM
users u
LEFT OUTER JOIN user_countries uc ON (u.email = uc.email)
GROUP BY u.email, u.valid, u.role
ORDER BY u.email;
CREATE VIEW suggestions_info_by_country_view AS SELECT c.val AS country,
coalesce(t1.n_duplicates, 0) AS n_duplicates,
coalesce(t2.n_conflicts, 0) AS n_conflicts
FROM countries c
LEFT OUTER JOIN (SELECT o.country AS country, count(DISTINCT d.*) AS n_duplicates FROM oa_duplicates d LEFT OUTER JOIN organizations o ON (d.local_id = o.id) GROUP BY o.country) AS t1 ON (t1.country = c.val)
LEFT OUTER JOIN (SELECT o.country AS country, count(DISTINCT c.idgroup) AS n_conflicts FROM oa_conflicts c LEFT OUTER JOIN organizations o ON (c.id1 = o.id) GROUP BY o.country) AS t2 ON (t2.country = c.val);
CREATE VIEW conflict_groups_view AS SELECT
c.idgroup AS idgroup,
o1.id AS id_1,
o1.name AS name_1,
o1.type AS type_1,
o1.city AS city_1,
o1.country AS country_1,
o2.id AS id_2,
o2.name AS name_2,
o2.type AS type_2,
o2.city AS city_2,
o2.country AS country_2
FROM
oa_conflicts c
LEFT OUTER JOIN organizations o1 ON (c.id1 = o1.id)
LEFT OUTER JOIN organizations o2 ON (c.id2 = o2.id)
WHERE
o1.id IS NOT NULL AND O2.id IS NOT NULL AND c.idgroup IS NOT NULL;
CREATE VIEW duplicate_groups_view AS SELECT
o.id,
o.name,
o.city,
o.country,
count(d.*) as n_duplicates
FROM
oa_duplicates d
LEFT OUTER JOIN organizations o ON (o.id = d.local_id)
WHERE
d.reltype = 'suggested'
GROUP BY o.id, o.name, o.city, o.country
ORDER BY o.name;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,14 @@
<table class="table table-sm table-hover col-lg-3">
<thead class="thead-light">
<tr>
<th>{{field}}</th>
<th class="text-right">#</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="e in entries">
<td><a href="#!{{resultsBasePath}}/0/50/{{e.value}}">{{e.value}}</a></td>
<td class="text-right">{{e.count}}</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,14 @@
<h4>{{info.name}}</h4>
<div class="alert alert-success" ng-if="message">{{message}}</div>
<p class="text-muted" ng-if="!message">
<b>ID: </b>{{info.id}}<br /> <b>Created at</b> {{info.creationDate | date:'MMMM d, y HH:mm:ss'}} <b>by</b> {{info.createdBy}}<br /> <b>Modified at</b> {{info.modificationDate | date:'MMMM d, y HH:mm:ss'}} <b>by</b>
{{info.modifiedBy}}
</p>
<div class="card">
<org-tabs-menu org-id="{{orgId}}" info="info" org="org" events="events" selected="currentTab"></org-tabs-menu>
<org-form-metadata org-id="{{orgId}}" org="org" vocabularies="vocabularies" info-method="getInfo()" ng-if="currentTab == 1"></org-form-metadata>
<org-dedup-events org-id="{{orgId}}" events="events" vocabularies="vocabularies" info-method="getInfo()" ng-if="currentTab == 2"></org-dedup-events>
</div>

View File

@ -0,0 +1,40 @@
<div>
<div class="input-group input-group-sm mb-3">
<input type="text" class="form-control" ng-model="conflictFilter" placeholder="Filter...">
<div class="input-group-append">
<span class="input-group-text text-outline-primary" id="inputGroup-sizing-sm">Country:</span>
<button class="btn btn-outline-primary dropdown-toggle" data-toggle="dropdown">{{country}}</button>
<div class="dropdown-menu">
<small>
<a class="dropdown-item" href="#!/suggestions/{{c}}/2"
ng-repeat="(c, vals) in info.byCountry"
ng-if="vals.nConflicts > 0">
{{c}} <span class="badge badge-danger float-right">{{vals.nConflicts}}</span>
</a>
</small>
</div>
</div>
</div>
<div class="card text-white mb-3" ng-repeat="w in conflicts | filter:conflictFilter" class="mb-2">
<div class="card-header bg-primary text-white py-1">Group {{$index+1}}</div>
<table class="table table-sm">
<tr ng-repeat="o in w">
<th style="width:40px" class="text-center">#{{$index+1}}</th>
<td><a href="#!/edit/0/{{o.id}}" title="{{o.id}}">{{o.name}}</a></td>
<td style="width:250px"><img ng-src="resources/images/flags/{{o.country}}.gif" /> {{o.city}}, {{o.country}}</td>
<td style="width:80px" class="text-right">{{o.type}}</td>
</tr>
</table>
<div class="card-footer bg-secondary py-1">
<button type="button"
class="btn btn-sm btn-primary"
data-toggle="modal" data-target="#resolveConflictsModal"
ng-click="prepareConflictsModal(w)">resolve</button>
</div>
</div>
</div>
<resolve-conflicts-modal modal-id="resolveConflictsModal" orgs="orgs" selected-orgs="selectedOrgs"></resolve-conflicts-modal>

View File

@ -0,0 +1,59 @@
<div>
<div class="input-group input-group-sm mb-3">
<input type="text" class="form-control" ng-model="duplicateFilter" placeholder="Filter...">
<div class="input-group-append">
<span class="input-group-text text-outline-primary" id="inputGroup-sizing-sm">Country:</span>
<button class="btn btn-outline-primary dropdown-toggle" data-toggle="dropdown">{{country}}</button>
<div class="dropdown-menu">
<small>
<a class="dropdown-item" href="#!/suggestions/{{c}}/1"
ng-repeat="(c, vals) in info.byCountry"
ng-if="vals.nDuplicates > 0">
{{c}} <span class="badge badge-primary float-right">{{vals.nDuplicates}}</span>
</a>
</small>
</div>
</div>
</div>
<table class="table table-sm table-hover">
<thead class="thead-light">
<tr class="d-flex">
<th class="col-8">Organization</th>
<th class="col-3">Place</th>
<th class="col-1 text-right"># pending duplicates</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="d in duplicates | filter:duplicateFilter" class="d-flex">
<td class="col-8">
<a href="javascript:void(0)" title="{{d.id}}" ng-click="prepareDuplicatesModal(d)" data-toggle="modal" data-target="#duplicatesModal">{{d.name}}</a>
<a href="#!/edit/0/{{d.id}}" title="edit"><i class="fa fa-edit"></i></a>
</td>
<td class="col-3"><img ng-src="resources/images/flags/{{d.country}}.gif" /> {{d.city}}, {{d.country}}</td>
<td class="col-1 text-right">{{d.numberOfDuplicates}}</td>
</tr>
</tbody>
</table>
</div>
<div class="modal fade" id="duplicatesModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{currentOrg.name}}</h5>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<div class="text-muted" ng-if="currentDuplicates.length == 0">No duplicates</div>
<org-details org="currentOrgDetails" org-title="Registered organization" show="default"></org-details>
<org-form-duplicates duplicates="currentDuplicates"></org-form-duplicates>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" data-dismiss="modal" ng-click="saveCurrentDuplicates()" ng-if="currentDuplicates.length > 0">Save changes</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,28 @@
<div class="card mb-3">
<div class="card-header bg-primary text-white py-1">Conflicts</div>
<div class="card-body text-muted" ng-if="conflicts.length == 0">No conflicts</div>
<table class="table table-sm mt-2" ng-if="conflicts.length > 0">
<thead>
<tr class="d-flex">
<th class="col-6 pl-3" style="border-top: none;">Related organization</th>
<th class="col-2 text-center" style="border-top: none;">Type</th>
<th class="col-4 text-right" style="border-top: none;">Place</th>
</tr>
</thead>
<tbody>
<tr class="d-flex" ng-repeat="sr in conflicts">
<td class="col-6 pl-3">{{sr.name}}<br /><a href="#!/edit/0/{{sr.id}}" class="small text-monospace">{{sr.id}}</a></td>
<td class="col-2 text-center small">{{sr.type}}</td>
<td class="col-4 text-right small"><img ng-src="resources/images/flags/{{sr.country}}.gif" /> {{sr.city}}, {{sr.country}}</td>
</tr>
</tbody>
</table>
<div class="card-footer text-right" ng-if="showSaveButton && conflicts.length > 0">
<button class="btn btn-sm btn-primary" data-toggle="modal" data-target="#resolveConflictsModal" ng-click="prepareConflictsModal()">resolve conflicts</button>
</div>
</div>
<resolve-conflicts-modal modal-id="resolveConflictsModal" orgs="candidateConflicts" selected-orgs="selectedConflicts"></resolve-conflicts-modal>

View File

@ -0,0 +1,45 @@
<div class="card mb-3">
<div class="card-header bg-primary text-white py-1">Duplicates</div>
<div class="text-muted card-body" ng-if="duplicates.length == 0">No duplicates</div>
<table class="table table-sm mt-2" ng-if="duplicates.length > 0">
<thead>
<tr class="d-flex">
<th class="col-4 pl-3" style="border-top: none;">Related organization</th>
<th class="col-1 text-center" style="border-top: none;">Acronym</th>
<th class="col-2 text-center" style="border-top: none;">Country</th>
<th class="col-3" style="border-top: none;">Source</th>
<th class="col-2" style="border-top: none;"></th>
</tr>
</thead>
<tbody>
<tr class="d-flex" ng-repeat="sr in duplicates">
<td class="col-4 pl-3">{{sr.oaName}} <span class="small" ng-if="sr.oaUrl"><br />
<b>URL: </b><a href="{{sr.oaUrl}}" target="_blank">{{sr.oaUrl}}</a></span>
</td>
<td class="col-1 text-center small">{{sr.oaAcronym}}</td>
<td class="col-2 text-center small"><img ng-src="resources/images/flags/{{sr.oaCountry}}.gif" /> {{sr.oaCountry}}</td>
<td class="col-3 small"><b>Collected from:</b> {{sr.oaCollectedFrom}}<br /> <b>Original Id:</b> <span class="text-monospace">{{sr.oaOriginalId}}</span></td>
<td class="col-2 text-right">
<div class="btn-group btn-group-toggle btn-group-sm" data-toggle="buttons">
<label class="btn" ng-class="{'btn-danger' : sr.relType == 'is_different', 'btn-outline-danger' : sr.relType != 'is_different'}"> <input type="radio" autocomplete="off" ng-model="sr.relType"
value="is_different" ng-class="{'active' : sr.relType == 'is_different'}"><i class="fas fa-times fa-fw"></i>
</label>
<label class="btn" ng-class="{'btn-info' : sr.relType == 'suggested', 'btn-outline-info' : sr.relType != 'suggested'}"> <input type="radio" autocomplete="off" ng-model="sr.relType"
value="suggested" ng-class="{'active' : sr.relType == 'suggested'}"><i class="fas fa-question fa-fw"></i>
</label>
<label class="btn" ng-class="{'btn-success' : sr.relType == 'is_similar', 'btn-outline-success' : sr.relType != 'is_similar'}"> <input type="radio" autocomplete="off" ng-model="sr.relType"
value="is_similar" ng-class="{'active' : sr.relType == 'is_similar'}" /><i class="fas fa-check fa-fw"></i>
</label>
</div>
</td>
</tr>
</tbody>
</table>
<div class="card-footer text-right" ng-if="showSaveButton && duplicates.length > 0">
<button class="btn btn-sm btn-primary" ng-click="saveFunction()">save</button>
</div>
</div>

View File

@ -0,0 +1,282 @@
<div class="card-body">
<form name="organizationForm">
<fieldset>
<legend>Official name and type</legend>
<div class="form-group">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<div class="input-group-text bg-primary text-white">name</div>
</div>
<input name="org_nm" type="text" class="form-control"
placeholder="organization name..." ng-model="org.name"
required="required"
ng-class="{'is-invalid' : organizationForm.org_nm.$error.required}" />
<div class="input-group-append input-group-prepend">
<div class="input-group-text bg-primary text-white">type</div>
</div>
<select name="org_tp" class="custom-select" ng-model="org.type"
required="required"
ng-class="{'is-invalid' : organizationForm.org_tp.$error.required}">
<option disabled="disabled" value=''>type...</option>
<option ng-repeat="t in vocabularies.orgTypes">{{t}}</option>
</select>
</div>
</div>
</fieldset>
<fieldset class="mt-4">
<legend>Geographical location</legend>
<div class="form-group">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<div class="input-group-text bg-primary text-white">city</div>
</div>
<input name="org_ct" type="text" class="form-control" placeholder=""
ng-model="org.city" />
<div class="input-group-append input-group-prepend">
<div class="input-group-text bg-primary text-white">country</div>
</div>
<select name="org_cntr" class="custom-select" ng-model="org.country"
required="required"
ng-class="{'is-invalid' : organizationForm.org_cntr.$error.required}">
<option disabled="disabled" value=''>country...</option>
<option ng-repeat="c in vocabularies.countries">{{c}}</option>
</select>
<div class="input-group-append input-group-prepend">
<div class="input-group-text bg-primary text-white">lat</div>
</div>
<input name="org_lat" type="text" class="form-control"
placeholder="0.0" ng-model="org.lat" />
<div class="input-group-append input-group-prepend">
<div class="input-group-text bg-primary text-white">lng</div>
</div>
<input name="org_lng" type="text" class="form-control"
placeholder="0.0" ng-model="org.lng" />
</div>
</div>
</fieldset>
<fieldset class="mt-4">
<legend>Other names and identifiers</legend>
<div class="form-group row">
<div class="col-lg-8 mb-2">
<div class="card">
<div class="card-header bg-primary text-white text-center py-1">Acronyms</div>
<table class="table table-sm">
<tbody>
<tr ng-repeat="a in org.acronyms">
<td>{{a}}</td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-danger"
ng-click="org.acronyms.splice($index, 1)">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr ng-init="newAcronym=''">
<td><input type="text" class="form-control form-control-sm"
placeholder="new acronym..." ng-model='newAcronym' /></td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-success"
ng-click="org.acronyms.push(newAcronym); newAcronym=''"
ng-disabled="!newAcronym || org.acronyms.indexOf(newAcronym) !== -1">
<i class="fa fa-plus"></i>
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-lg-8 mb-2">
<div class="card">
<div class="card-header bg-primary text-white text-center py-1">Aliases</div>
<table class="table table-sm">
<thead class="thead-light">
<tr>
<th>name</th>
<th style="width: 156px">language</th>
<th style="width: 44px"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="n in org.otherNames">
<td>{{n.name}}</td>
<td>{{n.lang}}</td>
<td class="text-right">
<button type="button" class="btn btn-sm btn-outline-danger"
ng-click="org.otherNames.splice($index, 1)">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr ng-init="newOtherName=newLang=''">
<td><input type="text" class="form-control form-control-sm"
placeholder="new alias..." ng-model="newOtherName" /></td>
<td><select class="custom-select custom-select-sm"
ng-model="newLang">
<option disabled="disabled" value=''>language...</option>
<option ng-repeat="l in vocabularies.languages">{{l}}</option>
</select></td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-success"
ng-click="org.otherNames.push({'name': newOtherName, 'lang': newLang}); newOtherName=newLang=''"
ng-disabled="!newOtherName || !newLang">
<i class="fa fa-plus"></i>
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-lg-8 mb-2">
<div class="card">
<div class="card-header bg-primary text-white text-center py-1">Identifiers</div>
<table class="table table-sm">
<thead class="thead-light">
<tr>
<th>id</th>
<th style="width: 156px">type</th>
<th style="width: 44px"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="id in org.otherIdentifiers">
<td>{{id.id}}</td>
<td>{{id.type}}</td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-danger"
ng-click="org.otherIdentifiers.splice($index, 1)">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr ng-init="newId=newIdType=''">
<td><input type="text" class="form-control form-control-sm"
placeholder="new id..." ng-model="newId" /></td>
<td><select class="custom-select custom-select-sm"
ng-model="newIdType">
<option disabled="disabled" value=''>type...</option>
<option ng-repeat="t in vocabularies.idTypes">{{t}}</option>
</select></td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-success"
ng-click="org.otherIdentifiers.push({'id': newId, 'type': newIdType}); newId=newIdType=''"
ng-disabled="!newId || !newIdType">
<i class="fa fa-plus"></i>
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-lg-8 mb-2">
<div class="card">
<div class="card-header bg-primary text-white text-center py-1">Urls</div>
<table class="table table-sm">
<tbody>
<tr ng-repeat="u in org.urls">
<td><a href="{{u}}" target="_blank">{{u}}</a></td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-danger"
ng-click="org.urls.splice($index, 1)">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr ng-init="newUrl=''">
<td><input type="text" class="form-control form-control-sm"
placeholder="http://..." ng-model="newUrl" /></td>
<td class="text-right" style="width: 44px">
<button type="button" class="btn btn-sm btn-outline-success"
ng-click="org.urls.push(newUrl); newUrl=''"
ng-disabled="!newUrl || org.urls.indexOf(newUrl) !== -1">
<i class="fa fa-plus"></i>
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</fieldset>
<fieldset class="mt-4">
<legend>Relations</legend>
<div class="form-group row">
<div class="col-lg-8 mb-2">
<div class="card">
<div class="card-header bg-primary text-white text-center py-1">Relations</div>
<table class="table table-sm">
<thead class="thead-light">
<tr>
<th class="pl-3" style="border-top: none;">Related organization</th>
<th style="border-top: none; width: 156px">Type</th>
<th style="border-top: none; width: 44px"></th>
</tr>
</thead>
<tbody>
<tr ng-if="org.relations.length == 0">
<td class="text-center text-muted" colspan="3">No relations</td>
</tr>
<tr ng-repeat="r in org.relations">
<td class="pl-3"><a href="#!/edit/0/{{r.relatedOrgId}}" title="{{r.relatedOrgId}}">{{r.relatedOrgName}}</a></td>
<td>{{r.type}}</td>
<td class="text-right">
<button type="button" class="btn btn-sm btn-outline-danger" ng-click="org.relations.splice($index, 1)"><i class="fa fa-trash"></i></button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>
<input type="text" placeholder="add a related organization..." readonly="readonly"
class="form-control form-control-sm" style="background-color: white; color: #007bff;"
ng-model="newRelation.name" ng-click="resetSelectedRelation()"
data-toggle="modal" data-target="#selectRelatedOrgModal"/>
</td>
<td>
<select class="custom-select custom-select-sm" ng-model="newRelType">
<option disabled="disabled" value=''>rel type...</option>
<option ng-repeat="t in vocabularies.relTypes">{{t}}</option>
</select>
</td>
<td class="text-right">
<button type="button" class="btn btn-sm btn-outline-success" ng-disabled="!newRelType || !newRelation.id" ng-click="addNewRelation()"><i class="fa fa-plus"></i></button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</fieldset>
<button type="submit" class="btn btn-primary" ng-click="save()" ng-disabled="organizationForm.$invalid">save</button>
</form>
</div>
<select-org-modal modal-id="selectRelatedOrgModal" selected-org="newRelation"></select-org-modal>

View File

@ -0,0 +1,10 @@
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item">
<a href="javascript:void(0)" class="nav-link" ng-class="{'active': selected == 1}" ng-click="loadOrg()">Metadata Management</a>
</li>
<li class="nav-item">
<a href="javascript:void(0)" class="nav-link" ng-class="{'active': selected == 2}" ng-click="loadDedupEvents()">Dedup Events <span class="badge badge-primary ml-2">{{info.nDuplicates}}</span> <span class="badge badge-danger">{{info.nConflicts}}</span></a>
</li>
</ul>
</div>

View File

@ -0,0 +1,51 @@
<div class="modal fade" id="{{modalId}}" tabindex="-1" role="dialog">
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Resolve conflicts</h5>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body" style="min-height: 300px">
<div class="row">
<div class="col-6">
<div class="card border-primary">
<div class="card-header text-white bg-primary">Suggested organizations</div>
<div class="card-body" ng-repeat="o in orgs | filter:{show:'secondary'}" ng-class="{'pb-0' : $index==0 , 'py-0' : $index > 0 }">
<org-details org="o" org-title="{{o.id}}" show="{{o.show}}"></org-details>
<div class="text-right mb-3">
<button type="button" class="btn btn-sm btn-primary" ng-click="selectOrg(o)">select >></button>
</div>
</div>
<div class="card-body text-secondary text-center" ng-show="selectedOrgs.length == orgs.length">no organization(s)</div>
</div>
</div>
<div class="col-6">
<div class="card border-primary">
<div class="card-header text-white bg-primary">New group</div>
<div class="card-body" ng-repeat="o in selectedOrgs" ng-class="{'pb-0' : $index==0 , 'py-0' : $index > 0 }">
<org-details org="o" org-title="{{($index==0)?'Master organization':'Secondary organization ' + $index}}" show="{{o.show}}"></org-details>
</div>
<div class="card-body" ng-show="selectedOrgs.length == 0">
<div class="card border-secondary">
<div class="card-body text-secondary text-center">select master organization</div>
</div>
</div>
<div class="card-body pt-0" ng-show="selectedOrgs.length == 1">
<div class="card border-secondary">
<div class="card-body text-secondary text-center">select secondary organization(s)</div>
</div>
</div>
<div class="card-footer" ng-show="selectedOrgs.length > 0">
<button type="button" class="btn btn-sm btn-secondary" ng-click="reset()" ng-show="selectedOrgs.length > 0">reset</button>
<button type="button" class="btn btn-sm btn-primary float-right" ng-click="createGroup()" ng-show="selectedOrgs.length > 1">create group</button>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,27 @@
<div class="modal fade" id="{{modalId}}" tabindex="-1" role="dialog">
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Select Organization</h5>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body" style="min-height: 300px">
<div class="input-group input-group-sm">
<input type="text" class="form-control" ng-model="searchText" />
<div class="input-group-append">
<button type="button" class="btn btn-outline-primary" ng-click="search(searchText, 0, 25)">Search</button>
</div>
</div>
<div ng-show="searchValue">
<org-results orgs="searchOrgs"
prev-function="search(searchValue, searchOrgs.number - 1, searchOrgs.size)"
next-function="search(searchValue, searchOrgs.number + 1, searchOrgs.size)"
selected-org="selectedOrg"
mode="select-modal"></org-results>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="card">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item"><a href="javascript:void(0)" class="nav-link active">Metadata</a></li>
</ul>
</div>
<org-form-metadata org="org" vocabularies="vocabularies" mode="insert"></org-form-metadata>
</div>

View File

@ -0,0 +1,11 @@
<div class="card-body">
<org-details org="currentOrgDetails" org-title="Current organization" show="default"></org-details>
<org-form-duplicates duplicates="events.duplicates" save-function="saveDuplicates()" show-save-button="1"></org-form-duplicates>
<org-form-conflicts conflicts="events.conflicts" save-function="saveConflicts()" org-id="{{currentOrgDetails.id}}" show-save-button="1"></org-form-conflicts>
</div>

View File

@ -0,0 +1,34 @@
<div class="card mb-3" ng-show="show != 'hidden'">
<div class="card-header py-1" ng-class="{
'text-white bg-primary' : show == 'default',
'text-white bg-success' : show == 'success',
'text-white bg-info' : show == 'info',
'bg-secondary' : show == 'secondary',
}">{{orgTitle}}</div>
<table class="table table-sm table-condensed">
<tr class="d-flex">
<th class="col-4 pl-3">Name</th>
<td class="col-8">{{org.name}}</td>
</tr>
<tr class="d-flex">
<th class="col-4 pl-3">Type</th>
<td class="col-8">{{org.type}}</td>
</tr>
<tr class="d-flex">
<th class="col-4 pl-3">Place</th>
<td class="col-8">{{org.city}}, {{org.country}}</td>
</tr>
<tr class="d-flex">
<th class="col-4 pl-3">Acronyms</th>
<td class="col-8">{{org.acronyms.join(', ')}}</td>
</tr>
<tr class="d-flex">
<th class="col-4 pl-3">Also known as</th>
<td class="col-8"><span ng-repeat="n in org.otherNames">{{n.name}}<br /></span></td>
</tr>
<tr class="d-flex">
<th class="col-4 pl-3">Urls</th>
<td class="col-8"><span ng-repeat="u in org.urls"><a href="{{u}}" target="_blank">{{u}}</a><br /></span></td>
</tr>
</table>
</div>

View File

@ -0,0 +1,49 @@
<p ng-if="mode != 'select-modal'">
<span ng-if="orgs.totalElements > 0">Page: {{orgs.number + 1}} / {{orgs.totalPages}}<br />Total elements: {{orgs.totalElements}}<br />{{searchMessage}}</span>
<span ng-if="orgs.totalElements == 0">Page: -<br />Total elements: 0<br />{{searchMessage}}</span>
<span ng-if="!(orgs.totalElements || orgs.totalElements === 0)">Page:<br />Total elements:<br />{{searchMessage}}</span>
</p>
<p ng-if="mode == 'select-modal'" class="text-right">
<span ng-if="orgs.totalElements > 0"><b>Page:</b> {{orgs.number + 1}} / {{orgs.totalPages}} <b>- Total elements:</b> {{orgs.totalElements}}</span>
<span ng-if="orgs.totalElements == 0"><b>Total elements:</b> 0</span>
</p>
<h4 ng-if="orgs.totalElements == 0" class="text-center">
No results
</h4>
<div ng-if="orgs.totalElements > 0">
<nav>
<ul class="pagination justify-content-center">
<li class="page-item" ng-class="{'disabled' : orgs.first }">
<a class="page-link" href="javascript:void(0)" ng-click="prevFunction()">&laquo; Previous</a>
</li>
<li class="page-item" ng-class="{'disabled' : orgs.last }">
<a class="page-link" href="javascript:void(0)" ng-click="nextFunction()">Next &raquo;</a>
</li>
</ul>
</nav>
<table class="table table-sm table-hover">
<thead class="thead-light">
<tr class="d-flex">
<th class="col-6">Organization name</th>
<th class="col-4">Place</th>
<th class="col-1 text-center">Acronyms</th>
<th class="col-1 text-right">Type</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="o in orgs.content" class="d-flex">
<td class="col-6">
<a ng-if="mode == 'select-modal'" href="javascript:void(0)" title="select" ng-click="selectOrg(o)" data-dismiss="modal">{{o.name}}</a>
<a ng-if="mode != 'select-modal'" href="#!/edit/0/{{o.id}}" title="{{o.id}}">{{o.name}}</a>
</td>
<td class="col-4"><img ng-src="resources/images/flags/{{o.country}}.gif" /> {{o.city}}, {{o.country}}</td>
<td class="col-1 text-center">{{o.acronyms.join()}}</td>
<td class="col-1 text-right">{{o.type}}</td>
</tr>
</tbody>
</table>
</div>

View File

@ -0,0 +1,4 @@
<org-results search-message="Searching for country: {{fieldValue}}"
orgs="orgs"
prev-function="prev()"
next-function="next()"></org-results>

View File

@ -0,0 +1,4 @@
<org-results search-message="Searching for type: {{fieldValue}}"
orgs="orgs"
prev-function="prev()"
next-function="next()"></org-results>

View File

@ -0,0 +1,13 @@
<div class="row">
<div class="offset-md-1 offset-lg-2 offset-xl-4 col-md-10 col-lg-8 col-xl-4">
<form style="margin-top:200px;" ng-submit="search()">
<div class="input-group input-group-lg">
<input type="text" class="form-control" ng-model="searchText" autofocus="true" />
<div class="input-group-append">
<button type="submit" class="btn btn-outline-primary">Search</button>
</div>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,4 @@
<org-results search-message="Searching for: {{searchText}}"
orgs="orgs"
prev-function="prev()"
next-function="next()"></org-results>

View File

@ -0,0 +1,19 @@
<div class="card">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item text-nowrap">
<a href="#!/suggestions/_/1" class="nav-link" ng-class="{'active': currentTab == 1}">New Duplicates <span class="badge badge-primary ml-2">{{info.total.nDuplicates}}</span></a>
</li>
<li class="nav-item text-nowrap">
<a href="#!/suggestions/_/2" class="nav-link" ng-class="{'active': currentTab == 2}">New Conflicts <span class="badge badge-danger ml-2">{{info.total.nConflicts}}</span></a>
</li>
</ul>
</div>
<div class="card-body">
<all-duplicates duplicates="duplicates" country="{{country}}" info="info" info-method="getInfo()" ng-if="currentTab == 1"></all-duplicates>
<all-conflicts conflicts="conflicts" country="{{country}}" info="info" info-method="getInfo()" ng-if="currentTab == 2"></all-conflicts>
</div>
</div>

View File

@ -0,0 +1,116 @@
<h4>Users</h4>
<br />
<div class="row">
<div class="col-sm-12 col-md-10 col-lg-8">
<input type="text" class="form-control form-control-sm mb-3" ng-model="userFilter.email" placeholder="Filter...">
<table class="table table-sm table-hover">
<thead class="thead-light">
<tr class="d-flex">
<th class="col-4">User</th>
<th class="col-1 text-center">Enabled</th>
<th class="col-1 text-center">Super Admin</th>
<th class="col-1 text-center">National Admin</th>
<th class="col-3">Countries</th>
<th class="col-2"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="u in users | filter:userFilter" class="d-flex">
<th class="col-4" ng-class="{'text-secondary': !u.valid}">{{u.email}}</th>
<td class="col-1 text-center text-success">
<i class="fa fa-check-circle" ng-if="u.valid"></i>
<span class="text-warning" ng-if="u.role == 'PENDING'">not configured</span>
<span class="text-danger" ng-if="!u.valid && (u.role != 'PENDING')">disabled</span>
</td>
<td class="col-1 text-center">
<i class="fa fa-check-circle" ng-if="u.role == 'ADMIN'"></i>
</td>
<td class="col-1 text-center">
<i class="fa fa-check-circle" ng-if="u.role == 'NATIONAL_ADMIN'"></i>
</td>
<td class="col-3">
<span ng-if="(u.role != 'ADMIN')">
<img ng-src="resources/images/flags/{{c}}.gif" title="{{c}}" class="mr-1" ng-repeat="c in u.countries" />
<span class="text-warning" ng-if="u.countries.length == 0"><i class="fa fa-exclamation-triangle"></i> no countries</span>
</span>
<span class="text-muted" ng-if="u.role == 'ADMIN'">All countries</span>
</td>
<td class="col-2 text-right">
<a href="mailto:{{u.email}}" class="btn btn-sm btn-outline-info"><i class="fa fa-at"></i></a>
<button type="button" class="btn btn-sm btn-outline-info" data-toggle="modal" data-target="#editUserModal" ng-click="setCurrentUser(u)"><i class="fa fa-edit"></i></button>
<button type="button" class="btn btn-sm btn-outline-danger" ng-click="deleteUser(u.email)"><i class="fa fa-trash"></i></button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="modal fade" id="editUserModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit user</h5>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body" style="min-height: 300px; max-height: 500px; overflow-y: auto;">
<form>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10"><a href="mailto:{{currentUser.email}}">{{currentUser.email}}</a></div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Enabled</label>
<div class="col-sm-10">
<div class="form-check">
<input class="form-check-input" type="checkbox" ng-model="currentUser.valid" />
</div>
</div>
</div>
<div class="form-group row" ng-if="superAdminMode && currentUser.valid">
<label class="col-sm-2 col-form-label">Role</label>
<div class="col-sm-10">
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" value="USER" ng-model="currentUser.role">
<label class="form-check-label">USER</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" value="NATIONAL_ADMIN" ng-model="currentUser.role">
<label class="form-check-label">NATIONAL ADMIN</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" value="ADMIN" ng-model="currentUser.role">
<label class="form-check-label">SUPER ADMIN</label>
</div>
</div>
</div>
<div class="card mb-3" ng-if="currentUser.valid && ((currentUser.role == 'USER') || (currentUser.role == 'NATIONAL_ADMIN'))">
<div class="card-header bg-primary text-white py-1">Countries</div>
<div class="card-body">
<div class="form-group row">
<div class="col-xs-6 col-sm-3 col-md-2 col-lg-1" ng-repeat="c in vocabularies.countries">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" checklist-model="currentUser.countries" checklist-value="c"/>
<label class="form-check-label">{{c}}</label>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" data-dismiss="modal" ng-click="saveUser(currentUser)">Save changes</button>
</div>
</div>
</div>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 B

Some files were not shown because too many files have changed in this diff Show More