Browse Source

added chart, added full statistics endpoint, refactoring

develop
bvn13 2 years ago
parent
commit
0010beca9f
  1. 5
      buildit
  2. 73
      covid19-api/src/main/java/com/bvn13/covid19/api/controllers/ApiController.java
  3. 0
      covid19-site/.gitignore
  4. 1
      covid19-site/build.gradle
  5. 0
      covid19-site/gradlew
  6. 0
      covid19-site/gradlew.bat
  7. 6
      covid19-site/src/main/java/com/bvn13/covid19/site/Covid19SiteApplication.java
  8. 14
      covid19-site/src/main/java/com/bvn13/covid19/site/config/MainConfig.java
  9. 95
      covid19-site/src/main/java/com/bvn13/covid19/site/controllers/AllStatsController.java
  10. 59
      covid19-site/src/main/java/com/bvn13/covid19/site/controllers/LastStatsController.java
  11. 16
      covid19-site/src/main/java/com/bvn13/covid19/site/controllers/MainController.java
  12. 23
      covid19-site/src/main/java/com/bvn13/covid19/site/controllers/RegionsController.java
  13. 2
      covid19-site/src/main/java/com/bvn13/covid19/site/converters/ZonedDateTimeToStringConverter.java
  14. 2
      covid19-site/src/main/java/com/bvn13/covid19/site/dtos/CovidUpdateInfoDto.java
  15. 15
      covid19-site/src/main/java/com/bvn13/covid19/site/model/CovidAllStats.java
  16. 14
      covid19-site/src/main/java/com/bvn13/covid19/site/model/CovidData.java
  17. 20
      covid19-site/src/main/java/com/bvn13/covid19/site/model/CovidDayStats.java
  18. 2
      covid19-site/src/main/java/com/bvn13/covid19/site/repositories/CovidStatsRepository.java
  19. 14
      covid19-site/src/main/java/com/bvn13/covid19/site/repositories/CovidUpdatesRepository.java
  20. 9
      covid19-site/src/main/java/com/bvn13/covid19/site/repositories/RegionsRepository.java
  21. 43
      covid19-site/src/main/java/com/bvn13/covid19/site/service/CovidStatsMaker.java
  22. 25
      covid19-site/src/main/java/com/bvn13/covid19/site/service/CovidStatsResponseMaker.java
  23. 3
      covid19-site/src/main/resources/application.yaml
  24. 1
      covid19-site/src/main/resources/static/css/chart.min.css
  25. 7
      covid19-site/src/main/resources/static/js/chart.min.js
  26. 1
      covid19-site/src/main/resources/static/js/fetch.min.js
  27. 2
      covid19-site/src/main/resources/static/js/jquery-3.5.0.min.js
  28. 17112
      covid19-site/src/main/resources/static/js/lodash.js
  29. 1
      covid19-site/src/main/resources/static/js/polyfill.min.js
  30. 158
      covid19-site/src/main/resources/templates/main-page.html
  31. 4
      covid19-site/src/test/java/com/bvn13/covid19/site/Covid19SiteApplicationTests.java
  32. 0
      covid19-site/src/test/resources/application-test.yaml
  33. 4
      settings.gradle

5
buildit

@ -3,12 +3,11 @@ @@ -3,12 +3,11 @@
version=0.0.2
./gradlew :covid19-db-migrator:clean :covid19-db-migrator:assemble
./gradlew :covid19-api:clean :covid19-api:assemble
./gradlew :covid19-site:clean :covid19-site:assemble
./gradlew :covid19-scheduler:clean :covid19-scheduler:assemble
[ ! -d "build" ] && mkdir "build";
cp "covid19-db-migrator/build/libs/covid19-db-migrator-$version.jar" "build/covid19-db-migrator-$version.jar"
cp "covid19-api/build/libs/covid19-api-$version.jar" "build/covid19-api-$version.jar"
cp "covid19-site/build/libs/covid19-site-$version.jar" "build/covid19-site-$version.jar"
cp "covid19-scheduler/build/libs/covid19-scheduler-$version.jar" "build/covid19-scheduler-$version.jar"

73
covid19-api/src/main/java/com/bvn13/covid19/api/controllers/ApiController.java

@ -1,73 +0,0 @@ @@ -1,73 +0,0 @@
/*
Copyright [2020] [bvn13]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.bvn13.covid19.api.controllers;
import com.bvn13.covid19.api.model.CovidStatsInfo;
import com.bvn13.covid19.api.model.CovidStatsResponse;
import com.bvn13.covid19.api.service.CovidStatsMaker;
import com.bvn13.covid19.model.entities.CovidStat;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.time.ZoneId;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@RestController
@RequestMapping("/api")
public class ApiController {
private final CovidStatsMaker covidStatsMaker;
@Value("${app.zone-id}")
private String zoneIdStr;
private ZoneId zoneId;
@PostConstruct
public void init() {
zoneId = ZoneId.of(zoneIdStr);
}
@GetMapping
public CovidStatsResponse getStatistics() {
return covidStatsMaker.findLastUpdateInfo()
.map(updateInfo -> CovidStatsResponse.builder()
.datetime(updateInfo.getDatetime())
.updatedOn(updateInfo.getCreatedOn().atZone(zoneId))
.stats(convertStats(covidStatsMaker.findCovidStatsByUpdateInfoId(updateInfo.getId())))
.build())
.orElse(CovidStatsResponse.builder().build());
}
private List<CovidStatsInfo> convertStats(Collection<CovidStat> stats) {
return stats.stream()
.map(stat -> CovidStatsInfo.builder()
.region(stat.getRegion().getName())
.sick(stat.getSick())
.healed(stat.getHealed())
.died(stat.getDied())
.build())
.collect(Collectors.toList());
}
}

0
covid19-api/.gitignore → covid19-site/.gitignore vendored

1
covid19-api/build.gradle → covid19-site/build.gradle

@ -6,6 +6,7 @@ dependencies { @@ -6,6 +6,7 @@ dependencies {
compile(project(':covid19-model'))
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

0
covid19-api/gradlew → covid19-site/gradlew vendored

0
covid19-api/gradlew.bat → covid19-site/gradlew.bat vendored

6
covid19-api/src/main/java/com/bvn13/covid19/api/Covid19ApiApplication.java → covid19-site/src/main/java/com/bvn13/covid19/site/Covid19SiteApplication.java

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package com.bvn13.covid19.api;
package com.bvn13.covid19.site;
import com.bvn13.covid19.model.Covid19ModelConfig;
import org.springframework.boot.SpringApplication;
@ -27,10 +27,10 @@ import org.springframework.context.annotation.Import; @@ -27,10 +27,10 @@ import org.springframework.context.annotation.Import;
@Import({
Covid19ModelConfig.class
})
public class Covid19ApiApplication {
public class Covid19SiteApplication {
public static void main(String[] args) {
SpringApplication.run(Covid19ApiApplication.class, args);
SpringApplication.run(Covid19SiteApplication.class, args);
}
}

14
covid19-site/src/main/java/com/bvn13/covid19/site/config/MainConfig.java

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
package com.bvn13.covid19.site.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
public class MainConfig {
@Value("${app.main-url}")
private String mainUrl;
}

95
covid19-site/src/main/java/com/bvn13/covid19/site/controllers/AllStatsController.java

@ -0,0 +1,95 @@ @@ -0,0 +1,95 @@
package com.bvn13.covid19.site.controllers;
import com.bvn13.covid19.model.entities.CovidStat;
import com.bvn13.covid19.site.model.CovidAllStats;
import com.bvn13.covid19.site.model.CovidData;
import com.bvn13.covid19.site.model.CovidDayStats;
import com.bvn13.covid19.site.service.CovidStatsMaker;
import com.bvn13.covid19.site.service.CovidStatsResponseMaker;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@RestController
@RequestMapping("/stats")
public class AllStatsController {
private final CovidStatsMaker covidStatsMaker;
private final CovidStatsResponseMaker covidStatsResponseMaker;
@Value("${app.zone-id}")
private String zoneIdStr;
private ZoneId zoneId;
@PostConstruct
public void init() {
zoneId = ZoneId.of(zoneIdStr);
}
@GetMapping("/all")
public CovidAllStats getStatistics(@RequestParam("region") String regionName) {
if (StringUtils.isNotBlank(regionName)) {
return constructResponseForRegion(regionName);
} else {
return constructResponseForAllRegions();
}
}
private CovidAllStats constructResponseForRegion(String regionName) {
return CovidAllStats.builder()
.regions(Collections.singletonList(regionName))
.progress(covidStatsMaker.findAllLastUpdatesPerDay().stream()
.map(covidUpdate -> CovidDayStats.builder()
.datetime(covidUpdate.getDatetime())
.updatedOn(covidUpdate.getCreatedOn().atZone(zoneId))
.stats(convertStatsToData(findCovidStatsByUpdateIdAndRegion(covidUpdate.getId(), regionName)))
.build())
.sorted(CovidDayStats::compareTo)
.collect(Collectors.toList()))
.build();
}
private CovidAllStats constructResponseForAllRegions() {
return CovidAllStats.builder()
.regions(covidStatsMaker.findAllRegionsNames())
.progress(covidStatsMaker.findAllLastUpdatesPerDay().stream()
.map(covidUpdate -> CovidDayStats.builder()
.datetime(covidUpdate.getDatetime())
.updatedOn(covidUpdate.getCreatedOn().atZone(zoneId))
.stats(convertStatsToData(findCovidStatsByUpdateId(covidUpdate.getId())))
.build())
.sorted(CovidDayStats::compareTo)
.collect(Collectors.toList()))
.build();
}
private List<CovidData> convertStatsToData(Collection<CovidStat> stats) {
return covidStatsResponseMaker.convertStats(stats);
}
private List<CovidStat> findCovidStatsByUpdateIdAndRegion(long updateId, String region) {
return covidStatsMaker.findCovidStatsByUpdateInfoId(updateId).stream()
.filter(covidStat -> region.equals(covidStat.getRegion().getName()))
.collect(Collectors.toList());
}
private List<CovidStat> findCovidStatsByUpdateId(long updateId) {
return new ArrayList<>(covidStatsMaker.findCovidStatsByUpdateInfoId(updateId));
}
}

59
covid19-site/src/main/java/com/bvn13/covid19/site/controllers/LastStatsController.java

@ -0,0 +1,59 @@ @@ -0,0 +1,59 @@
/*
Copyright [2020] [bvn13]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.bvn13.covid19.site.controllers;
import com.bvn13.covid19.site.model.CovidDayStats;
import com.bvn13.covid19.site.service.CovidStatsMaker;
import com.bvn13.covid19.site.service.CovidStatsResponseMaker;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.time.ZoneId;
@RequiredArgsConstructor
@RestController
@RequestMapping("/stats")
public class LastStatsController {
private final CovidStatsMaker covidStatsMaker;
private final CovidStatsResponseMaker covidStatsResponseMaker;
@Value("${app.zone-id}")
private String zoneIdStr;
private ZoneId zoneId;
@PostConstruct
public void init() {
zoneId = ZoneId.of(zoneIdStr);
}
@GetMapping("/last")
public CovidDayStats getStatistics() {
return covidStatsMaker.findLastUpdate()
.map(covidUpdate -> CovidDayStats.builder()
.datetime(covidUpdate.getDatetime())
.updatedOn(covidUpdate.getCreatedOn().atZone(zoneId))
.stats(covidStatsResponseMaker.convertStats(covidStatsMaker.findCovidStatsByUpdateInfoId(covidUpdate.getId())))
.build())
.orElse(CovidDayStats.builder().build());
}
}

16
covid19-site/src/main/java/com/bvn13/covid19/site/controllers/MainController.java

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
package com.bvn13.covid19.site.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/")
public class MainController {
@GetMapping
public String mainPage() {
return "main-page";
}
}

23
covid19-site/src/main/java/com/bvn13/covid19/site/controllers/RegionsController.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
package com.bvn13.covid19.site.controllers;
import com.bvn13.covid19.site.service.CovidStatsMaker;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RequiredArgsConstructor
@RestController
@RequestMapping("/regions")
public class RegionsController {
private final CovidStatsMaker covidStatsMaker;
@GetMapping
public List<String> getAllRegions() {
return covidStatsMaker.findAllRegionsNames();
}
}

2
covid19-api/src/main/java/com/bvn13/covid19/api/converters/ZonedDateTimeToStringConverter.java → covid19-site/src/main/java/com/bvn13/covid19/site/converters/ZonedDateTimeToStringConverter.java

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package com.bvn13.covid19.api.converters;
package com.bvn13.covid19.site.converters;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

2
covid19-api/src/main/java/com/bvn13/covid19/api/dtos/CovidUpdateInfoDto.java → covid19-site/src/main/java/com/bvn13/covid19/site/dtos/CovidUpdateInfoDto.java

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package com.bvn13.covid19.api.dtos;
package com.bvn13.covid19.site.dtos;
import lombok.Value;

15
covid19-site/src/main/java/com/bvn13/covid19/site/model/CovidAllStats.java

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
package com.bvn13.covid19.site.model;
import lombok.Builder;
import lombok.Value;
import java.util.List;
@Builder
@Value
public class CovidAllStats {
List<String> regions;
List<CovidDayStats> progress;
}

14
covid19-api/src/main/java/com/bvn13/covid19/api/model/CovidStatsInfo.java → covid19-site/src/main/java/com/bvn13/covid19/site/model/CovidData.java

@ -14,28 +14,18 @@ See the License for the specific language governing permissions and @@ -14,28 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package com.bvn13.covid19.api.model;
package com.bvn13.covid19.site.model;
import lombok.Builder;
import lombok.Value;
import java.time.ZonedDateTime;
@Builder
@Value
public class CovidStatsInfo {
public class CovidData {
String region;
long sick;
long healed;
long died;
@Builder
@Value
public static class Delta {
long sick;
long healed;
long died;
}
}

20
covid19-api/src/main/java/com/bvn13/covid19/api/model/CovidStatsResponse.java → covid19-site/src/main/java/com/bvn13/covid19/site/model/CovidDayStats.java

@ -14,9 +14,10 @@ See the License for the specific language governing permissions and @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package com.bvn13.covid19.api.model;
package com.bvn13.covid19.site.model;
import lombok.Builder;
import lombok.NonNull;
import lombok.Singular;
import lombok.Value;
@ -25,11 +26,24 @@ import java.util.List; @@ -25,11 +26,24 @@ import java.util.List;
@Builder
@Value
public class CovidStatsResponse {
public class CovidDayStats implements Comparable<CovidDayStats> {
ZonedDateTime updatedOn;
ZonedDateTime datetime;
@Singular(value = "stats")
List<CovidStatsInfo> stats;
List<CovidData> stats;
@Override
public int compareTo(@NonNull CovidDayStats another) {
if (this.equals(another)) {
return 0;
} else {
return updatedOn.isBefore(another.getUpdatedOn())
? -1
: updatedOn.isEqual(another.getUpdatedOn())
? 0
: 1;
}
}
}

2
covid19-api/src/main/java/com/bvn13/covid19/api/repositories/CovidStatsRepository.java → covid19-site/src/main/java/com/bvn13/covid19/site/repositories/CovidStatsRepository.java

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package com.bvn13.covid19.api.repositories;
package com.bvn13.covid19.site.repositories;
import com.bvn13.covid19.model.entities.CovidStat;
import com.bvn13.covid19.model.entities.CovidUpdate;

14
covid19-api/src/main/java/com/bvn13/covid19/api/repositories/CovidUpdateInfosRepository.java → covid19-site/src/main/java/com/bvn13/covid19/site/repositories/CovidUpdatesRepository.java

@ -14,22 +14,26 @@ See the License for the specific language governing permissions and @@ -14,22 +14,26 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package com.bvn13.covid19.api.repositories;
package com.bvn13.covid19.site.repositories;
import com.bvn13.covid19.api.dtos.CovidUpdateInfoDto;
import com.bvn13.covid19.site.dtos.CovidUpdateInfoDto;
import com.bvn13.covid19.model.entities.CovidUpdate;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Optional;
@Repository
public interface CovidUpdateInfosRepository extends JpaRepository<CovidUpdate, Long> {
public interface CovidUpdatesRepository extends JpaRepository<CovidUpdate, Long> {
@Query("select new com.bvn13.covid19.api.dtos.CovidUpdateInfoDto(max(U.createdOn)) from CovidUpdate U")
Optional<CovidUpdateInfoDto> findLastUpdateInfo();
@Query("select new com.bvn13.covid19.site.dtos.CovidUpdateInfoDto(max(U.createdOn)) from CovidUpdate U")
Optional<CovidUpdateInfoDto> findLastUpdate();
@Query("select U from CovidUpdate U where U.id in (select max(U1.id) from CovidUpdate U1 group by U1.datetime)")
Collection<CovidUpdate> findAllLastUpdatesPerDay();
Optional<CovidUpdate> findFirstByCreatedOn(LocalDateTime createdOn);

9
covid19-site/src/main/java/com/bvn13/covid19/site/repositories/RegionsRepository.java

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
package com.bvn13.covid19.site.repositories;
import com.bvn13.covid19.model.entities.Region;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface RegionsRepository extends JpaRepository<Region, Long> {
}

43
covid19-api/src/main/java/com/bvn13/covid19/api/service/CovidStatsMaker.java → covid19-site/src/main/java/com/bvn13/covid19/site/service/CovidStatsMaker.java

@ -14,12 +14,14 @@ See the License for the specific language governing permissions and @@ -14,12 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package com.bvn13.covid19.api.service;
package com.bvn13.covid19.site.service;
import com.bvn13.covid19.api.repositories.CovidStatsRepository;
import com.bvn13.covid19.api.repositories.CovidUpdateInfosRepository;
import com.bvn13.covid19.model.entities.CovidStat;
import com.bvn13.covid19.model.entities.CovidUpdate;
import com.bvn13.covid19.model.entities.Region;
import com.bvn13.covid19.site.repositories.CovidStatsRepository;
import com.bvn13.covid19.site.repositories.CovidUpdatesRepository;
import com.bvn13.covid19.site.repositories.RegionsRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
@ -27,30 +29,33 @@ import org.springframework.stereotype.Component; @@ -27,30 +29,33 @@ import org.springframework.stereotype.Component;
import javax.transaction.Transactional;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@Component
public class CovidStatsMaker {
private final CovidStatsRepository covidRepository;
private final CovidUpdateInfosRepository updatesRepository;
private final CovidStatsRepository statsRepository;
private final CovidUpdatesRepository updatesRepository;
private final RegionsRepository regionsRepository;
@Transactional
public Collection<CovidStat> getLastCovidStats() {
return updatesRepository.findLastUpdateInfo()
return updatesRepository.findLastUpdate()
.flatMap(updateInfo -> updatesRepository.findFirstByCreatedOn(updateInfo.getCreatedOn()))
.map(covidRepository::findAllByUpdateInfo)
.map(statsRepository::findAllByUpdateInfo)
.orElse(Collections.emptyList());
}
@Cacheable(
cacheNames = "covid-last-update-info",
cacheNames = "covid-last-update",
unless = "#result == null"
)
@Transactional
public Optional<CovidUpdate> findLastUpdateInfo() {
return updatesRepository.findLastUpdateInfo()
public Optional<CovidUpdate> findLastUpdate() {
return updatesRepository.findLastUpdate()
.flatMap(updateInfo -> updatesRepository.findFirstByCreatedOn(updateInfo.getCreatedOn()));
}
@ -60,7 +65,23 @@ public class CovidStatsMaker { @@ -60,7 +65,23 @@ public class CovidStatsMaker {
unless = "#result == null || #result.size() <= 0"
)
public Collection<CovidStat> findCovidStatsByUpdateInfoId(long updateInfoId) {
return covidRepository.findAllByUpdateInfo_Id(updateInfoId);
return statsRepository.findAllByUpdateInfo_Id(updateInfoId);
}
@Cacheable(
cacheNames = "covid-all-days-updates",
unless = "#result == null || #result.size() <= 0"
)
public Collection<CovidUpdate> findAllLastUpdatesPerDay() {
return updatesRepository.findAllLastUpdatesPerDay();
}
@Cacheable(
cacheNames = "covid-regions",
unless = "#result == null || #result.size() <= 0"
)
public List<String> findAllRegionsNames() {
return regionsRepository.findAll().stream().map(Region::getName).collect(Collectors.toList());
}
}

25
covid19-site/src/main/java/com/bvn13/covid19/site/service/CovidStatsResponseMaker.java

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
package com.bvn13.covid19.site.service;
import com.bvn13.covid19.model.entities.CovidStat;
import com.bvn13.covid19.site.model.CovidData;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class CovidStatsResponseMaker {
public List<CovidData> convertStats(Collection<CovidStat> stats) {
return stats.stream()
.map(stat -> CovidData.builder()
.region(stat.getRegion().getName())
.sick(stat.getSick())
.healed(stat.getHealed())
.died(stat.getDied())
.build())
.collect(Collectors.toList());
}
}

3
covid19-api/src/main/resources/application.yaml → covid19-site/src/main/resources/application.yaml

@ -3,6 +3,7 @@ server: @@ -3,6 +3,7 @@ server:
app:
zone-id: Europe/Moscow
main-url: http://localhost:8080
spring:
application:
@ -12,7 +13,7 @@ spring: @@ -12,7 +13,7 @@ spring:
type: caffeine
caffeine:
spec: expireAfterWrite=15m
cache-names: covid-last-update-info, covid-stats-by-update-info-id
cache-names: covid-last-update, covid-all-days-updates, covid-stats-by-update-info-id, covid-regions
flyway:
enabled: false

1
covid19-site/src/main/resources/static/css/chart.min.css vendored

@ -0,0 +1 @@ @@ -0,0 +1 @@
@keyframes chartjs-render-animation{from{opacity:.99}to{opacity:1}}.chartjs-render-monitor{animation:chartjs-render-animation 1ms}.chartjs-size-monitor,.chartjs-size-monitor-expand,.chartjs-size-monitor-shrink{position:absolute;direction:ltr;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1}.chartjs-size-monitor-expand>div{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}

7
covid19-site/src/main/resources/static/js/chart.min.js vendored

File diff suppressed because one or more lines are too long

1
covid19-site/src/main/resources/static/js/fetch.min.js vendored

File diff suppressed because one or more lines are too long

2
covid19-site/src/main/resources/static/js/jquery-3.5.0.min.js vendored

File diff suppressed because one or more lines are too long

17112
covid19-site/src/main/resources/static/js/lodash.js

File diff suppressed because it is too large Load Diff

1
covid19-site/src/main/resources/static/js/polyfill.min.js vendored

@ -0,0 +1 @@ @@ -0,0 +1 @@
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n():"function"==typeof define&&define.amd?define(n):n()}(0,function(){"use strict";function e(e){var n=this.constructor;return this.then(function(t){return n.resolve(e()).then(function(){return t})},function(t){return n.resolve(e()).then(function(){return n.reject(t)})})}function n(e){return!(!e||"undefined"==typeof e.length)}function t(){}function o(e){if(!(this instanceof o))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=undefined,this._deferreds=[],c(e,this)}function r(e,n){for(;3===e._state;)e=e._value;0!==e._state?(e._handled=!0,o._immediateFn(function(){var t=1===e._state?n.onFulfilled:n.onRejected;if(null!==t){var o;try{o=t(e._value)}catch(r){return void f(n.promise,r)}i(n.promise,o)}else(1===e._state?i:f)(n.promise,e._value)})):e._deferreds.push(n)}function i(e,n){try{if(n===e)throw new TypeError("A promise cannot be resolved with itself.");if(n&&("object"==typeof n||"function"==typeof n)){var t=n.then;if(n instanceof o)return e._state=3,e._value=n,void u(e);if("function"==typeof t)return void c(function(e,n){return function(){e.apply(n,arguments)}}(t,n),e)}e._state=1,e._value=n,u(e)}catch(r){f(e,r)}}function f(e,n){e._state=2,e._value=n,u(e)}function u(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var n=0,t=e._deferreds.length;t>n;n++)r(e,e._deferreds[n]);e._deferreds=null}function c(e,n){var t=!1;try{e(function(e){t||(t=!0,i(n,e))},function(e){t||(t=!0,f(n,e))})}catch(o){if(t)return;t=!0,f(n,o)}}var a=setTimeout;o.prototype["catch"]=function(e){return this.then(null,e)},o.prototype.then=function(e,n){var o=new this.constructor(t);return r(this,new function(e,n,t){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof n?n:null,this.promise=t}(e,n,o)),o},o.prototype["finally"]=e,o.all=function(e){return new o(function(t,o){function r(e,n){try{if(n&&("object"==typeof n||"function"==typeof n)){var u=n.then;if("function"==typeof u)return void u.call(n,function(n){r(e,n)},o)}i[e]=n,0==--f&&t(i)}catch(c){o(c)}}if(!n(e))return o(new TypeError("Promise.all accepts an array"));var i=Array.prototype.slice.call(e);if(0===i.length)return t([]);for(var f=i.length,u=0;i.length>u;u++)r(u,i[u])})},o.resolve=function(e){return e&&"object"==typeof e&&e.constructor===o?e:new o(function(n){n(e)})},o.reject=function(e){return new o(function(n,t){t(e)})},o.race=function(e){return new o(function(t,r){if(!n(e))return r(new TypeError("Promise.race accepts an array"));for(var i=0,f=e.length;f>i;i++)o.resolve(e[i]).then(t,r)})},o._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){a(e,0)},o._unhandledRejectionFn=function(e){void 0!==console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)};var l=function(){if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if("undefined"!=typeof global)return global;throw Error("unable to locate global object")}();"Promise"in l?l.Promise.prototype["finally"]||(l.Promise.prototype["finally"]=e):l.Promise=o});

158
covid19-site/src/main/resources/templates/main-page.html

@ -0,0 +1,158 @@ @@ -0,0 +1,158 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Home page</title>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<!-- <link rel="stylesheet" th:href="@{/css/chart.min.css}">-->
</head>
<body>
<p>
<span th:text="'Today is: ' + ${#dates.format(#dates.createNow(), 'dd MMM yyyy HH:mm')}" th:remove="tag"></span>
</p>
<p>
<a th:href="${@mainConfig.getMainUrl()} + '/stats/last'">Последние данные</a>
</p>
<p>
<a th:href="${@mainConfig.getMainUrl()} + '/stats/all'">Все данные</a>
</p>
<p>
<label for="region">Регион</label>
<select id="region"></select>
</p>
<div style="width: 75%;">
<canvas id="chart"></canvas>
</div>
<script th:inline="javascript">
var mainUrl = [[${@mainConfig.getMainUrl()}]];
document.addEventListener('DOMContentLoaded', () => {
(function ($) {
function setUpRegions(regions) {
$("#region > option").each((i, el) => {
$(el).remove();
});
_.forEach(regions, (region) => {
$("#region").append("<option value='" + region + "'>" + region + "</option>");
});
onRegionChanged();
}
function getRegion() {
return document.getElementById("region").value;
}
function onRegionChanged() {
var region = getRegion();
loadStatsForRegion(region);
}
function loadStatsForRegion(region) {
fetch(mainUrl + '/stats/all?region=' + region)
.then(value => {
return value.json()
})
.then(json => {
console.log(json);
showData(json);
});
}
function showData(json) {
var labels = _.map(json.progress, (progress) => progress.datetime.substr(0, 10));
var sick = _.map(json.progress, (progress) => progress.stats.length > 0 ? progress.stats[0].sick : 0);
var healed = _.map(json.progress, (progress) => progress.stats.length > 0 ? progress.stats[0].healed : 0);
var died = _.map(json.progress, (progress) => progress.stats.length > 0 ? progress.stats[0].died : 0);
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Всего',
data: sick,
backgroundColor: 'red',
borderColor: 'red',
borderWidth: 1,
fill: false
}, {
label: 'Выздоровело',
data: healed,
backgroundColor: 'green',
borderColor: 'green',
borderWidth: 1,
fill: false
}, {
label: 'Умерло',
data: died,
backgroundColor: 'black',
borderColor: 'black',
borderWidth: 1,
fill: false
}]
},
options: {
responsive: true,
elements: {
line: {
tension: 0.000001
}
},
scales: {
xAxes: [{
display: true,
scaleLabel: {
display: true,
labelString: 'Дата'
}
}],
yAxes: [{
display: true,
scaleLabel: {
display: true,
labelString: 'Количество (чел.)'
},
ticks: {
beginAtZero: true
}
}]
}
}
});
myChart.canvas.parentNode.style.width = '1024px';
// myChart.canvas.parentNode.style.height = '1024px';
}
$("#region").change(() => onRegionChanged());
var ctx = document.getElementById('chart').getContext('2d');
fetch(mainUrl + '/regions')
.then(value => {
return value.json()
})
.then(json => setUpRegions(json));
})(jQuery);
});
</script>
<script th:src="@{/js/jquery-3.5.0.min.js}"></script>
<script th:src="@{/js/polyfill.min.js}"></script>
<script th:src="@{/js/fetch.min.js}"></script>
<script th:src="@{/js/chart.min.js}"></script>
<script th:src="@{/js/lodash.js}"></script>
</body>
</html>

4
covid19-api/src/test/java/com/bvn13/covid19/api/Covid19ApiApplicationTests.java → covid19-site/src/test/java/com/bvn13/covid19/site/Covid19SiteApplicationTests.java

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
package com.bvn13.covid19.api;
package com.bvn13.covid19.site;
import com.bvn13.covid19.model.Covid19ModelConfig;
import org.junit.jupiter.api.Test;
@ -14,7 +14,7 @@ import org.springframework.test.context.support.AnnotationConfigContextLoader; @@ -14,7 +14,7 @@ import org.springframework.test.context.support.AnnotationConfigContextLoader;
classes = { Covid19ModelConfig.class },
loader = AnnotationConfigContextLoader.class)
@Profile("test")
class Covid19ApiApplicationTests {
class Covid19SiteApplicationTests {
@Test
void contextLoads() {

0
covid19-api/src/test/resources/application-test.yaml → covid19-site/src/test/resources/application-test.yaml

4
settings.gradle

@ -8,5 +8,5 @@ rootProject.name = 'covid19' @@ -8,5 +8,5 @@ rootProject.name = 'covid19'
include ':covid19-model'
include ':covid19-db-migrator'
include ':covid19-api'
include ':covid19-scheduler'
include ':covid19-site'
include ':covid19-scheduler'

Loading…
Cancel
Save