Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/#19 dictionaries tops #45

Merged
merged 26 commits into from
Jan 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
93e5669
feat: #19 Added /get-stats-overview description to OAS. Added a /get-…
dmitry-weirdo Dec 29, 2020
a5f659b
feat: #19 Added OpenAPI Generator for the OAS. Fixed "required" decla…
dmitry-weirdo Dec 29, 2020
8adcb0c
feat: #19 OpenAPI-generated sources are added to the build. Added Ass…
dmitry-weirdo Dec 29, 2020
c695f82
test: #19 In JacksonUtilsTest, completely replace PlayerIndexData wit…
dmitry-weirdo Dec 29, 2020
f08b37f
refactor: #19 Completely replaced usage of PlayerIndexData in kgparse…
dmitry-weirdo Dec 29, 2020
16088f3
refactor: #19 Completely replaced usage of PlayerIndexData. Class del…
dmitry-weirdo Dec 29, 2020
5d73ad2
test: #19 Added OAS for /get-summary (with some hacks in response mod…
dmitry-weirdo Dec 29, 2020
36bdbf9
refactor: #19 PlayerJsonData, PlayerJsonParser and PlayerJsonParserTe…
dmitry-weirdo Dec 29, 2020
68b4163
test: #19 PlayerMapperTest migrated from PlayerSummary to GetSummaryR…
dmitry-weirdo Dec 29, 2020
1ba37da
test: #19 In JacksonUtilsTest, extracted methods for different API en…
dmitry-weirdo Dec 29, 2020
961c773
test: #19 Started first test of /get-stats-overview response parsing.
dmitry-weirdo Dec 29, 2020
b28659b
test: #19 Added a test-case for parsing GetStatsOverviewResponse for …
dmitry-weirdo Dec 29, 2020
af8b8b8
feat: #19 added PlayerVocabularyStatsEntity, linked as @ManyToOne to …
dmitry-weirdo Dec 30, 2020
c154a52
feat: #19 added first part of "gametypes" validation. Some corer-case…
dmitry-weirdo Dec 30, 2020
3252db5
fix: #19 added GetStatsOverviewGameType#id to OAS.
dmitry-weirdo Dec 30, 2020
bd09f1e
feat: #19 added validation of gametypes.info. Added GetStatsOverviewR…
dmitry-weirdo Dec 31, 2020
30b2f41
feat: #19 avg_speed can also be null for 0 rases in normal, e.g. for …
dmitry-weirdo Dec 31, 2020
2840afa
feat: #19 Do not fail on "avg_speed: null" and "avg_error: null".
dmitry-weirdo Dec 31, 2020
05934e4
feat: #19 Do not fail on `type: ""` for non-standard vocabularies.
dmitry-weirdo Dec 31, 2020
a4eeff7
feat: #19 Do not fail on "best_speed: null".
dmitry-weirdo Dec 31, 2020
5c321d6
feat: #19 Do not fail on negative "symbols" for non-standard dictiona…
dmitry-weirdo Dec 31, 2020
d05bd10
feat: #19 Added mapping of GetStatsOverviewGameType to PlayerVocabula…
dmitry-weirdo Dec 31, 2020
a8b4baa
feat: #19 implemented batch cascade save of PlayerEntity and PlayerVo…
dmitry-weirdo Jan 1, 2021
cf94082
feat: #19 some non-generated manual top list pages added (just used t…
dmitry-weirdo Jan 1, 2021
3af59d1
feat: #19 Add temporary pages with vocabularies statistics.
dmitry-weirdo Jan 3, 2021
4a59fea
feat: #27 Change S3 bucket to klavostat.com. Added script that copies…
dmitry-weirdo Jan 3, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,9 @@ nb-configuration.xml
## OS X
##############################
.DS_Store


##############################
## My private ignore directory, to be able to use temp files
##############################
.ignoreme/
2 changes: 1 addition & 1 deletion .run/KgParserApplication.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<configuration default="false" name="KgParserApplication" type="Application" factoryName="Application" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="ru.klavogonki.kgparser.springboot.KgParserApplication" />
<module name="kgparser-springboot" />
<option name="PROGRAM_PARAMETERS" value="C:\java\kg\home\ec2-user\kg 20001 625000 &quot;2020-12-08 02-39-07&quot;" />
<option name="PROGRAM_PARAMETERS" value="d:/kg/2020.12.28 1 628000 1 &quot;2020-12-28 00-28-13&quot; " />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="ru.klavogonki.kgparser.export.*" />
Expand Down
2 changes: 1 addition & 1 deletion .run/PlayerDataDownloader.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<option name="PROGRAM_PARAMETERS" value="$PROJECT_DIR$/../kg/ 100000 110000 100" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="ru.klavogonki.kgparser.jsonParser.entity.*" />
<option name="PATTERN" value="ru.klavogonki.kgparser.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
Expand Down
2 changes: 1 addition & 1 deletion .run/PlayerJsonParser.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<option name="PROGRAM_PARAMETERS" value="C:\java\kg\home\ec2-user\kg 1 625000 &quot;2020-12-08 02-39-07&quot;" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="ru.klavogonki.kgparser.jsonParser.entity.*" />
<option name="PATTERN" value="ru.klavogonki.kgparser.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
Expand Down
7 changes: 7 additions & 0 deletions kgparserSpringBoot/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- Enable javax.validation in SpringBoot -->
<!-- see https://nullbeans.com/how-to-use-java-bean-validation-in-spring-boot/ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,47 @@
package ru.klavogonki.kgparser.jsonParser.entity;

import lombok.Data;
import org.hibernate.annotations.NaturalId;
import ru.klavogonki.kgparser.Rank;
import ru.klavogonki.kgparser.http.UrlConstructor;

import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.time.LocalDateTime;
import java.util.List;

@Data
@Entity
@Table(name = "Player")
public class PlayerEntity {

@Id
@GeneratedValue
// @GeneratedValue
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "pl_SEQ")
@SequenceGenerator(name = "pl_SEQ", sequenceName = "pl_SEQ", allocationSize = 1000)
private Long dbId;

private LocalDateTime importDate; // when PlayerDataDownloader has been executed

private String getSummaryError; // we import all users, including non-existing

private String getIndexDataError; // we import all users, including non-existing and users with failed /get-indexData
private String getIndexDataError; // we import all users, including non-existing and users with failed /get-index-data

private String getStatsOverviewError; // we import all users, including non-existing and users with failed /get-stats-overview

// fields from PlayerSummary
@NaturalId
private Integer playerId; // KG user id

private String login;
Expand Down Expand Up @@ -75,6 +86,10 @@ public class PlayerEntity {

private Integer carsCount;

@OneToMany(cascade = CascadeType.ALL)
// @JoinColumn(name = "player_id") // do not create a join table! // todo: it's hard for non-pk column, does not work easily. Investigate this
List<PlayerVocabularyStatsEntity> stats;

@Transient
public String getProfileLink() {
return UrlConstructor.userProfileLink(playerId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package ru.klavogonki.kgparser.jsonParser.entity;

import lombok.Data;
import ru.klavogonki.kgparser.Dictionary;
import ru.klavogonki.kgparser.DictionaryMode;
import ru.klavogonki.kgparser.NonStandardDictionaryType;

import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import java.time.LocalDateTime;

@Data
@Entity
@Table(name = "Player_Vocabulary_Stats")
public class PlayerVocabularyStatsEntity {
@Id
// @GeneratedValue
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "pvs_SEQ")
@SequenceGenerator(name = "pvs_SEQ", sequenceName = "pvs_SEQ", allocationSize = 1000)
private Long dbId;

private LocalDateTime importDate; // when PlayerDataDownloader has been executed

// Caused by: org.hibernate.DuplicateMappingException: Table [player_vocabulary_stats] contains physical column name [player_id] referred to by multiple logical column names: [player_id], [playerId]
// private Integer playerId; // KG user id

@ManyToOne
// @JoinColumn(name = "player_id", referencedColumnName = "player_id")
@JoinColumn(nullable = false, name = "player_id", referencedColumnName = "playerId") // weird behaviour, db column name fails, entity field name is working.
private PlayerEntity player;

private String error; // also duplicated in PlayerEntity#getStatsOverviewError

// todo: this can be extracted to a separate VocabularyEntity

private String vocabularyCode;

private Integer vocabularyId; // for non-standard dictionaries only

private Integer vocabularyInfoId; // probably the db id from KG database, gametype[i].info.id

private String vocabularyName;

@Enumerated(EnumType.STRING)
private NonStandardDictionaryType vocabularyType;

private Integer vocabularySymbols; // for non-standard dictionaries only

private Integer vocabularyRows; // for non-standard dictionaries only

/**
* <ul>
* <li>Для Обычного, Букв, Абракадабры, Яндекс.Рефератов, Цифр — {@code normal}</li>
* <li>Для Безошибочного — {@code noerror}</li>
* <li>Для Спринта — {@code sprint}</li>
* <li>Для Марафона — {@code marathon}</li>
* <li>Для нестандартных словарей — {@code normal}</li>
* </ul>
*
* @see DictionaryMode#getDictionaryMode
*/
@Enumerated(EnumType.STRING)
private DictionaryMode vocabularyMode;

/**
* <ul>
* <li>Для Обычного, Безошибочного, Спринта, Марафона — {@code 0}</li>
* <li>Для Абракадабры — {@code -1}</li>
* <li>Для Цифр — {@code -2}</li>
* <li>Для Яндекс.Рефератов — {@code -3}</li>
* <li>Для Букв — {@code -4}</li>
* <li>Для нестандартных словарей — числовой id словаря.</li>
* </ul>
*
* @see Dictionary#getTextType
*/
private Integer vocabularyTextType;

private Integer racesCount; // пробег игрока по словарю

private Double averageSpeed; // средняя скорость игрока по словарю, знаков в минуту

private Integer bestSpeed; // рекорд игрока по словарю, знаков в минуту

private Double averageError; // процент ошибок игрока по словарю, в процентах

private Integer haul; // время, проведённое игроком в словаре, в секундах

private Integer qual; // на сколько пройдена квалификация по словарю. Рекорды в словаре засчитываются в пределах 1.2 * qual.

private Integer dirty; // todo: wtf is this?

private LocalDateTime updated; // Время апдейта результата игроков по словарю. Московское время.

private Boolean bookDone; // пройдена ли книга. Заполнено только для словарей-книг.
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import ru.klavogonki.kgparser.PlayerJsonData;
import ru.klavogonki.kgparser.jsonParser.PlayerIndexData;
import ru.klavogonki.kgparser.jsonParser.entity.PlayerEntity;
import ru.klavogonki.kgparser.util.DateUtils;
import ru.klavogonki.openapi.model.Microtime;

import java.time.LocalDateTime;

Expand All @@ -14,7 +14,7 @@ public interface PlayerMapper {

// todo: add importDate?
/* Yes, MapStruct works with public fields - a great library! */
// PlayerSummary fields
// GetSummaryResponse fields
@Mapping(source = "summary.err", target = "getSummaryError")
@Mapping(source = "summary.user.id", target = "playerId")
@Mapping(source = "summary.user.login", target = "login")
Expand All @@ -24,19 +24,24 @@ public interface PlayerMapper {
@Mapping(source = "summary.title", target = "title")
@Mapping(source = "summary.blocked", target = "blocked")

// PlayerIndexData fields
// GetIndexDataResponse fields
@Mapping(source = "indexData.err", target = "getIndexDataError")
@Mapping(source = "indexData.stats.registered", target = "registered")
@Mapping(source = "indexData.stats.achievementsCount", target = "achievementsCount")
@Mapping(source = "indexData.stats.totalRacesCount", target = "totalRacesCount")
@Mapping(source = "indexData.stats.achievesCnt", target = "achievementsCount")
@Mapping(source = "indexData.stats.totalNumRaces", target = "totalRacesCount")
@Mapping(source = "indexData.stats.bestSpeed", target = "bestSpeed")
@Mapping(source = "indexData.stats.ratingLevel", target = "ratingLevel")
@Mapping(source = "indexData.stats.friendsCount", target = "friendsCount")
@Mapping(source = "indexData.stats.vocabulariesCount", target = "vocabulariesCount")
@Mapping(source = "indexData.stats.carsCount", target = "carsCount")
@Mapping(source = "indexData.stats.friendsCnt", target = "friendsCount")
@Mapping(source = "indexData.stats.vocsCnt", target = "vocabulariesCount")
@Mapping(source = "indexData.stats.carsCnt", target = "carsCount")

// GetStatsOverviewResponse fields
@Mapping(source = "statsOverview.err", target = "getStatsOverviewError")
// other fields will be parsed to PlayerVocabularyStatsEntity in a separate parser

PlayerEntity playerJsonDataToPlayerEntity(PlayerJsonData data);

default LocalDateTime registeredToLocalDateTime(PlayerIndexData.Registered registered) {
default LocalDateTime registeredToLocalDateTime(Microtime registered) {
return DateUtils.convertUserRegisteredTime(registered);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package ru.klavogonki.kgparser.jsonParser.mapper;

import org.mapstruct.AfterMapping;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings;
import ru.klavogonki.kgparser.DictionaryMode;
import ru.klavogonki.kgparser.NonStandardDictionaryType;
import ru.klavogonki.kgparser.jsonParser.entity.PlayerEntity;
import ru.klavogonki.kgparser.jsonParser.entity.PlayerVocabularyStatsEntity;
import ru.klavogonki.kgparser.util.DateUtils;
import ru.klavogonki.openapi.model.GetStatsOverviewGameType;
import ru.klavogonki.openapi.model.GetStatsOverviewResponse;
import ru.klavogonki.openapi.model.NonStandardVocabularyType;
import ru.klavogonki.openapi.model.VocabularyMode;

import java.time.LocalDateTime;

@Mapper
public interface PlayerVocabularyStatsMapper {

// @Mapping(source = "", target = "vocabularyCode")
@Mapping(source = "id", target = "vocabularyId")
@Mapping(source = "name", target = "vocabularyName")
@Mapping(source = "numRaces", target = "racesCount")
@Mapping(source = "type", target = "vocabularyType")
@Mapping(source = "symbols", target = "vocabularySymbols")
@Mapping(source = "rows", target = "vocabularyRows")
@Mapping(source = "bookDone", target = "bookDone")

@Mapping(source = "info.id", target = "vocabularyInfoId")
// @Mapping(source = "info.userId", target = "playerId") // todo: map to PlayerEntity
@Mapping(source = "info.mode", target = "vocabularyMode")
@Mapping(source = "info.texttype", target = "vocabularyTextType")
// @Mapping(source = "info.numRaces", target = "") // we assume gameType.numRaces is the same, it is also validated
@Mapping(source = "info.avgSpeed", target = "averageSpeed")
@Mapping(source = "info.bestSpeed", target = "bestSpeed")
@Mapping(source = "info.avgError", target = "averageError")
@Mapping(source = "info.haul", target = "haul")
@Mapping(source = "info.qual", target = "qual")
@Mapping(source = "info.dirty", target = "dirty")
@Mapping(source = "info.updated", target = "updated", dateFormat = DateUtils.DATE_TIME_FORMAT_FOR_UI)
PlayerVocabularyStatsEntity statsGameTypeToEntity(
GetStatsOverviewGameType gameType,
@Context LocalDateTime importDate,
@Context GetStatsOverviewResponse response,
@Context String vocabularyCode,
@Context PlayerEntity player
);

@ValueMappings({
@ValueMapping(source = "WORDS", target = "words"),
@ValueMapping(source = "PHRASES", target = "phrases"),
@ValueMapping(source = "TEXTS", target = "texts"),
@ValueMapping(source = "URL", target = "url"),
@ValueMapping(source = "BOOK", target = "book"),
@ValueMapping(source = "GENERATOR", target = "generator"),
})
NonStandardDictionaryType nonStandardDictionaryType(NonStandardVocabularyType vocabularyType);

@ValueMappings({
@ValueMapping(source = "NORMAL", target = "normal"),
@ValueMapping(source = "NOERROR", target = "noerror"),
@ValueMapping(source = "SPRINT", target = "sprint"),
@ValueMapping(source = "MARATHON", target = "marathon"),
})
DictionaryMode dictionaryMode(VocabularyMode vocabularyMode);

@AfterMapping
default void fillContextData(
GetStatsOverviewGameType gameType,
@MappingTarget PlayerVocabularyStatsEntity entity,
@Context LocalDateTime importDate,
@Context GetStatsOverviewResponse response,
@Context String vocabularyCode,
@Context PlayerEntity player
) {
entity.setImportDate(importDate);
entity.setError(response.getErr());
entity.setVocabularyCode(vocabularyCode);

/*
// todo: do we need to set the whole playerEntity, at least with its dbId?
PlayerEntity playerEntity = new PlayerEntity();
playerEntity.setPlayerId(playerId);

*/
entity.setPlayer(player);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ru.klavogonki.kgparser.jsonParser.repository;

import org.springframework.data.repository.CrudRepository;
import ru.klavogonki.kgparser.jsonParser.entity.PlayerVocabularyStatsEntity;

public interface PlayerVocabularyStatsRepository extends CrudRepository<PlayerVocabularyStatsEntity, Long> {
}
Loading