diff --git a/src/main/java/com/alterdekim/hearthhack/component/StartupListener.java b/src/main/java/com/alterdekim/hearthhack/component/StartupListener.java index edab2c5..2055094 100644 --- a/src/main/java/com/alterdekim/hearthhack/component/StartupListener.java +++ b/src/main/java/com/alterdekim/hearthhack/component/StartupListener.java @@ -3,9 +3,12 @@ package com.alterdekim.hearthhack.component; import com.alterdekim.hearthhack.config.ObjectConfig; import com.alterdekim.hearthhack.config.FS; import com.alterdekim.hearthhack.config.ServerConfig; +import com.alterdekim.hearthhack.dbf.CardsDBF; import com.alterdekim.hearthhack.game.GameTag; import com.alterdekim.hearthhack.parser.CardsXmlParser; import com.alterdekim.hearthhack.parser.DBFParser; +import com.alterdekim.hearthhack.util.CardsObject; +import com.alterdekim.hearthhack.util.FormattedCards; import com.alterdekim.hearthhack.xml.CardsXML; import com.alterdekim.hearthhack.xml.XMLEntity; import com.alterdekim.hearthhack.xml.XMLTag; @@ -21,7 +24,10 @@ import javax.sql.DataSource; import java.io.File; import java.io.IOException; import java.nio.file.*; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @Slf4j @Component @@ -54,11 +60,23 @@ public class StartupListener { if( !Path.of(serverConfig.getWorkDir() + File.separator + FS.dataDir).toFile().exists() ) initXml(); dbfConfig.setCards(DBFParser.parseCards(serverConfig.getWorkDir() + File.separator + FS.dbfDir)); dbfConfig.setCardsXML(DBFParser.parse(serverConfig.getWorkDir() + File.separator + FS.dataDir + File.separator + "enUS.xml", CardsXML.class)); + unionCards(); } catch (Exception e) { log.error(e.getMessage()); } } + private void unionCards() { + CardsDBF dbf = dbfConfig.getCards(); + CardsXML xml = dbfConfig.getCardsXML(); + List objs = xml.getEntities() + .stream() + .map(e -> new CardsObject(e, dbf.findWithMiniGuid(e.getCardId()).orElse(null))) + .filter(c -> c.getDbfCard() != null) + .collect(Collectors.toList()); + dbfConfig.setFormattedCards(new FormattedCards(objs)); + } + private void initDbf() throws Exception { makeDir(FS.dbfDir); Files.walk(Path.of(serverConfig.getDbfPath())) diff --git a/src/main/java/com/alterdekim/hearthhack/component/processor/client/request/OpenBooster.java b/src/main/java/com/alterdekim/hearthhack/component/processor/client/request/OpenBooster.java index a54c062..0033ca2 100644 --- a/src/main/java/com/alterdekim/hearthhack/component/processor/client/request/OpenBooster.java +++ b/src/main/java/com/alterdekim/hearthhack/component/processor/client/request/OpenBooster.java @@ -2,7 +2,6 @@ package com.alterdekim.hearthhack.component.processor.client.request; import com.alterdekim.Protocol; import com.alterdekim.hearthhack.component.TcpConnection; -import com.alterdekim.hearthhack.dbf.DBFCard; import com.alterdekim.hearthhack.game.GameTag; import com.alterdekim.hearthhack.util.BattleNetPacket; import com.alterdekim.hearthhack.util.ClientRequestBody; @@ -10,45 +9,50 @@ import com.alterdekim.hearthhack.util.Util; import lombok.extern.slf4j.Slf4j; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; import static com.alterdekim.hearthhack.util.GameUtilities.generateNotification; @Slf4j public class OpenBooster extends ClientRequestParser { + + private final Protocol.Date d = Protocol.Date.newBuilder() + .setYear(2023) + .setMonth(1) + .setDay(3) + .setHours(0) + .setMin(0) + .setSec(0) + .build(); + @Override public void parse(BattleNetPacket packet, ClientRequestBody body, TcpConnection conn) throws Exception { // 226 BoosterContent log.info("BoosterContent: got"); - Protocol.Date d = Protocol.Date.newBuilder() - .setYear(2023) - .setMonth(1) - .setDay(3) - .setHours(0) - .setMin(0) - .setSec(0) - .build(); + Protocol.OpenBooster ob = Protocol.OpenBooster.parseFrom(body.getBody()); - // TODO: optimize this - // TODO: union dbf cards and cardsxml0 into one list - List l = Util.getRandomItems(conn.getDbfConfig().getCardsXML().findEntitiesByTagVal(GameTag.CARD_SET, 3).stream().filter(p -> { - Optional c = conn.getDbfConfig().getCards().findWithMiniGuid(p.getCardId()); - return c.isPresent() && c.get().findField("IS_COLLECTIBLE").isPresent() && c.get().findField("IS_COLLECTIBLE").get().getVal().equals("True"); - }).collect(Collectors.toList()), 5) - .stream() - .map(e -> conn.getDbfConfig().getCards().findWithMiniGuid(e.getCardId())) - .filter(Optional::isPresent) - .map(Optional::get) - .map(c -> Protocol.BoosterCard.newBuilder() + List l = Util.getRandomItems( + conn.getDbfConfig() + .getFormattedCards() + .findEntitiesByTagVal(GameTag.CARD_SET, 3) + .stream() + .filter(p -> p.getDbfCard().findField("IS_COLLECTIBLE").isPresent() && p.getDbfCard().findField("IS_COLLECTIBLE").get().getVal().equals("True")) + .collect(Collectors.toList()), 5) + .stream() + .map(c -> Protocol.BoosterCard.newBuilder() .setCardDef(Protocol.CardDef.newBuilder() .setAsset(Integer.parseInt( - c.getFields().stream().filter(p -> p.getColumn().equals("ID")).findFirst().get().getVal() - ) - ) + c.getDbfCard().getFields().stream().filter(p -> p.getColumn().equals("ID")).findFirst().get().getVal() + )) .setPremium(1)) - .setInsertDate(d).build()) - .collect(Collectors.toList()); + .setInsertDate(d) + .build() + ) + .peek(c -> conn.getUserService().addCardToCollection(conn.getUserId(), (long) c.getCardDef().getAsset(), c.getCardDef().getPremium() != 0, false)) + .collect(Collectors.toList()); + + conn.getUserService().removeBooster(conn.getUserId(), ob.getBoosterType()); + Protocol.BoosterContent response = Protocol.BoosterContent.newBuilder() .addAllList(l) .build(); diff --git a/src/main/java/com/alterdekim/hearthhack/component/processor/client/request/generic/Collection.java b/src/main/java/com/alterdekim/hearthhack/component/processor/client/request/generic/Collection.java index 6dd3751..d8f0fd2 100644 --- a/src/main/java/com/alterdekim/hearthhack/component/processor/client/request/generic/Collection.java +++ b/src/main/java/com/alterdekim/hearthhack/component/processor/client/request/generic/Collection.java @@ -3,16 +3,48 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic; import com.alterdekim.Protocol; import com.alterdekim.hearthhack.component.TcpConnection; import com.alterdekim.hearthhack.util.BattleNetPacket; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.stream.Collectors; import static com.alterdekim.hearthhack.util.GameUtilities.generateEmptyNotification; import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.COLLECTION; + +@Slf4j public class Collection extends GenericParser { + private final Protocol.Date d = Protocol.Date.newBuilder() + .setYear(2023) + .setMonth(1) + .setDay(3) + .setHours(0) + .setMin(0) + .setSec(0) + .build(); + @Override public void parseGenericRequest(int token, TcpConnection conn) throws Exception { // my collection - Protocol.Collection collection = Protocol.Collection.newBuilder().build(); + + List l = conn.getUserService().getCollectionOfUser(conn.getUserId()) + .stream() + .map(p -> Protocol.CardStack.newBuilder() + .setCount(p.getCount()) + .setLatestInsertDate(d) + .setNumSeen(p.getHasSeen() ? 1 : 0) + .setCardDef(Protocol.CardDef.newBuilder() + .setPremium(p.getIsPremium() ? 1 : 0) + .setAsset(p.getAssetId().intValue())) + .build()) + .collect(Collectors.toList()); + + log.info("Collection: {}", l); + + Protocol.Collection collection = Protocol.Collection.newBuilder() + .addAllStacks(l) + .build(); Protocol.Notification n = generateEmptyNotification(207); diff --git a/src/main/java/com/alterdekim/hearthhack/config/ObjectConfig.java b/src/main/java/com/alterdekim/hearthhack/config/ObjectConfig.java index 1297521..b406368 100644 --- a/src/main/java/com/alterdekim/hearthhack/config/ObjectConfig.java +++ b/src/main/java/com/alterdekim/hearthhack/config/ObjectConfig.java @@ -1,6 +1,8 @@ package com.alterdekim.hearthhack.config; import com.alterdekim.hearthhack.dbf.CardsDBF; +import com.alterdekim.hearthhack.util.CardsObject; +import com.alterdekim.hearthhack.util.FormattedCards; import com.alterdekim.hearthhack.xml.CardsXML; import lombok.Data; import org.springframework.context.annotation.Configuration; @@ -10,5 +12,6 @@ import org.springframework.context.annotation.Configuration; public class ObjectConfig { private CardsDBF cards; private CardsXML cardsXML; + private FormattedCards formattedCards; private String clientConfig; } diff --git a/src/main/java/com/alterdekim/hearthhack/dto/UserCardDTO.java b/src/main/java/com/alterdekim/hearthhack/dto/UserCardDTO.java new file mode 100644 index 0000000..a6af828 --- /dev/null +++ b/src/main/java/com/alterdekim/hearthhack/dto/UserCardDTO.java @@ -0,0 +1,18 @@ +package com.alterdekim.hearthhack.dto; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class UserCardDTO { + private Long assetId; + private Boolean isPremium; + private Integer count; + private Boolean hasSeen; +} diff --git a/src/main/java/com/alterdekim/hearthhack/entity/CardStack.java b/src/main/java/com/alterdekim/hearthhack/entity/CardStack.java deleted file mode 100644 index c7b3635..0000000 --- a/src/main/java/com/alterdekim/hearthhack/entity/CardStack.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.alterdekim.hearthhack.entity; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -@Entity -@Table(name = "card_stack") -public class CardStack { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(nullable=false) - private Integer asset; - - @Column(nullable=false) - private Integer premium; -} diff --git a/src/main/java/com/alterdekim/hearthhack/entity/UserCard.java b/src/main/java/com/alterdekim/hearthhack/entity/UserCard.java index 54cce35..4454132 100644 --- a/src/main/java/com/alterdekim/hearthhack/entity/UserCard.java +++ b/src/main/java/com/alterdekim/hearthhack/entity/UserCard.java @@ -1,5 +1,7 @@ package com.alterdekim.hearthhack.entity; +import com.alterdekim.hearthhack.dto.BoosterDTO; +import com.alterdekim.hearthhack.dto.UserCardDTO; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; @@ -12,6 +14,25 @@ import lombok.Setter; @AllArgsConstructor @Entity @Table(name = "ucards") +@SqlResultSetMapping( + name = "ucardEntityMapping", + classes = { + @ConstructorResult( + targetClass = UserCardDTO.class, + columns = { + @ColumnResult(name = "assetId", type = Long.class), + @ColumnResult(name = "isPremium", type = Boolean.class), + @ColumnResult(name = "count", type = Integer.class), + @ColumnResult(name = "hasSeen", type = Boolean.class) + } + ) + } +) +@NamedNativeQuery( + name = "UserCard.getCollectionOfUser", + query = "SELECT u.asset_id as assetId, u.is_premium as isPremium, COUNT(u.id) as count, MIN(u.has_seen) as hasSeen FROM ucards u WHERE u.user_id = :userId GROUP BY u.asset_id, u.is_premium", + resultSetMapping = "ucardEntityMapping" +) public class UserCard { @Id @@ -22,5 +43,18 @@ public class UserCard { private Long userId; @Column(nullable = false) - private Long cardStackId; + private Long assetId; + + @Column(nullable = false) + private Boolean isPremium; + + @Column(nullable = false) + private Boolean hasSeen = false; + + public UserCard(Long userId, Long assetId, Boolean isPremium, Boolean hasSeen) { + this.userId = userId; + this.assetId = assetId; + this.isPremium = isPremium; + this.hasSeen = hasSeen; + } } diff --git a/src/main/java/com/alterdekim/hearthhack/repository/BoosterRepository.java b/src/main/java/com/alterdekim/hearthhack/repository/BoosterRepository.java index 2ef5544..42a6016 100644 --- a/src/main/java/com/alterdekim/hearthhack/repository/BoosterRepository.java +++ b/src/main/java/com/alterdekim/hearthhack/repository/BoosterRepository.java @@ -4,9 +4,11 @@ import com.alterdekim.hearthhack.dto.BoosterDTO; import com.alterdekim.hearthhack.entity.Booster; import com.alterdekim.hearthhack.entity.HeroXP; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -14,4 +16,9 @@ import java.util.List; public interface BoosterRepository extends JpaRepository { @Query(nativeQuery = true) List findBoostersByUserId(@Param("userId") Long userId); + + @Transactional + @Modifying + @Query(value = "DELETE FROM Booster a WHERE a.id IN (SELECT c.lid FROM (SELECT MAX(b.id) AS lid FROM Booster b WHERE b.userId = :userId AND b.type = :type) c)") + void deleteOneByType(@Param("userId") Long userId, @Param("type") Integer type); } diff --git a/src/main/java/com/alterdekim/hearthhack/repository/UserCardRepository.java b/src/main/java/com/alterdekim/hearthhack/repository/UserCardRepository.java new file mode 100644 index 0000000..aa2c57f --- /dev/null +++ b/src/main/java/com/alterdekim/hearthhack/repository/UserCardRepository.java @@ -0,0 +1,18 @@ +package com.alterdekim.hearthhack.repository; + +import com.alterdekim.hearthhack.dto.UserCardDTO; +import com.alterdekim.hearthhack.entity.User; +import com.alterdekim.hearthhack.entity.UserCard; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface UserCardRepository extends JpaRepository { + + @Query(nativeQuery = true) + List getCollectionOfUser(@Param("userId") Long userId); +} diff --git a/src/main/java/com/alterdekim/hearthhack/security/SpringSecurity.java b/src/main/java/com/alterdekim/hearthhack/security/SpringSecurity.java index 42b9507..24b99c3 100644 --- a/src/main/java/com/alterdekim/hearthhack/security/SpringSecurity.java +++ b/src/main/java/com/alterdekim/hearthhack/security/SpringSecurity.java @@ -15,6 +15,8 @@ import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.savedrequest.HttpSessionRequestCache; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toH2Console; + @Configuration @EnableWebSecurity public class SpringSecurity { @@ -36,6 +38,7 @@ public class SpringSecurity { .requestMatchers("/login").permitAll() .requestMatchers("/rules").permitAll() .requestMatchers("/static/**").permitAll() + .requestMatchers(toH2Console()).permitAll() ).formLogin( form -> form .loginPage("/login") @@ -55,7 +58,8 @@ public class SpringSecurity { ) .exceptionHandling((exc) -> exc .accessDeniedHandler(accessDeniedHandler()) - .accessDeniedPage("/403")); + .accessDeniedPage("/403")) + .headers().frameOptions().disable(); return http.build(); } diff --git a/src/main/java/com/alterdekim/hearthhack/service/UserService.java b/src/main/java/com/alterdekim/hearthhack/service/UserService.java index fdd05f1..c943577 100644 --- a/src/main/java/com/alterdekim/hearthhack/service/UserService.java +++ b/src/main/java/com/alterdekim/hearthhack/service/UserService.java @@ -1,11 +1,9 @@ package com.alterdekim.hearthhack.service; import com.alterdekim.hearthhack.dto.BoosterDTO; +import com.alterdekim.hearthhack.dto.UserCardDTO; import com.alterdekim.hearthhack.dto.UserDTO; -import com.alterdekim.hearthhack.entity.Booster; -import com.alterdekim.hearthhack.entity.CardBack; -import com.alterdekim.hearthhack.entity.Role; -import com.alterdekim.hearthhack.entity.User; +import com.alterdekim.hearthhack.entity.*; import com.alterdekim.hearthhack.repository.*; import lombok.RequiredArgsConstructor; import org.checkerframework.checker.units.qual.C; @@ -28,6 +26,7 @@ public class UserService implements IService { private final DeckRepository deckRepository; private final CardBackRepository cardBackRepository; private final BoosterRepository boosterRepository; + private final UserCardRepository userCardRepository; public void saveUser(UserDTO userDto) { @@ -91,6 +90,18 @@ public class UserService implements IService { return boosterRepository.findBoostersByUserId(userId); } + public void removeBooster(Long userId, Integer boosterType) { + boosterRepository.deleteOneByType(userId, boosterType); + } + + public void addCardToCollection(Long userId, Long assetId, Boolean isPremium, Boolean hasSeen) { + this.userCardRepository.save(new UserCard(userId, assetId, isPremium, hasSeen)); + } + + public List getCollectionOfUser(Long userId) { + return this.userCardRepository.getCollectionOfUser(userId); + } + public List findAllUsers() { List users = userRepository.findAll(); return users.stream().map(this::convertEntityToDto) diff --git a/src/main/java/com/alterdekim/hearthhack/util/CardsObject.java b/src/main/java/com/alterdekim/hearthhack/util/CardsObject.java new file mode 100644 index 0000000..b096aa1 --- /dev/null +++ b/src/main/java/com/alterdekim/hearthhack/util/CardsObject.java @@ -0,0 +1,11 @@ +package com.alterdekim.hearthhack.util; + +import com.alterdekim.hearthhack.dbf.DBFCard; +import com.alterdekim.hearthhack.xml.XMLEntity; +import lombok.*; + +@Data +public class CardsObject { + private final XMLEntity entity; + private final DBFCard dbfCard; +} \ No newline at end of file diff --git a/src/main/java/com/alterdekim/hearthhack/util/FormattedCards.java b/src/main/java/com/alterdekim/hearthhack/util/FormattedCards.java new file mode 100644 index 0000000..dac102f --- /dev/null +++ b/src/main/java/com/alterdekim/hearthhack/util/FormattedCards.java @@ -0,0 +1,35 @@ +package com.alterdekim.hearthhack.util; + + +import com.alterdekim.hearthhack.game.GameTag; +import lombok.*; + +import java.util.List; +import java.util.stream.Collectors; + +@ToString +@Setter +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class FormattedCards { + private List objects; + + public List findEntitiesByTagVal(GameTag tag, Integer val) { + return this.objects.stream() + .filter(o -> o.getEntity().getTags() + .stream() + .anyMatch(f -> f.getEnumID().intValue() == tag.getValue().intValue() && f.getValue().intValue() == val.intValue()) + ) + .collect(Collectors.toList()); + } + + public List findEntitiesByTagVal(GameTag tag, String val) { + return this.objects.stream() + .filter(o -> o.getEntity().getTags() + .stream() + .anyMatch(f -> f.getEnumID().intValue() == tag.getValue().intValue() && val.equals(f.getVal())) + ) + .collect(Collectors.toList()); + } +}