diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 05e146d..7e76ef6 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -31,3 +31,64 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + build-backend-image: + needs: release + runs-on: ubuntu-latest + name: Build Backend Image + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Ensure full history is available + - name: Extract tag + run: | + TAG=$(git describe --tags --abbrev=0) + echo "TAG=$TAG" >> $GITHUB_ENV + - name: Checkout + uses: actions/checkout@v4 + - name: Login + uses: docker/login-action@v3 + with: + registry: git.kjan.de + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASS }} + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: backend/ + file: backend/.docker/Dockerfile + push: true + tags: | + git.kjan.de/szut/casino-backend:latest + git.kjan.de/szut/casino-backend:${{ env.TAG }} + + build-frontend-image: + needs: release + runs-on: ubuntu-latest + name: Build Frontend Image + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Ensure full history is available + - name: Extract tag + run: | + TAG=$(git describe --tags --abbrev=0) + echo "TAG=$TAG" >> $GITHUB_ENV + - name: Checkout + uses: actions/checkout@v4 + - name: Login + uses: docker/login-action@v3 + with: + registry: git.kjan.de + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASS }} + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: frontend/ + file: frontend/.docker/Dockerfile + push: true + tags: | + git.kjan.de/szut/casino-frontend:latest + git.kjan.de/szut/casino-frontend:${{ env.TAG }} diff --git a/backend/.docker/Dockerfile b/backend/.docker/Dockerfile index d6df4f7..cbb743f 100644 --- a/backend/.docker/Dockerfile +++ b/backend/.docker/Dockerfile @@ -1,17 +1,17 @@ -FROM gradle:jdk22 AS builder +FROM gradle:jdk23 AS builder WORKDIR /app -COPY gradlew build.gradle.kts settings.gradle.kts ./ +COPY gradlew build.gradle.kts settings.gradle.kts config ./ COPY gradle gradle RUN chmod +x gradlew -RUN ./gradlew dependencies +RUN gradle dependencies COPY src src -RUN ./gradlew clean build -x test +RUN gradle clean build -x test -x checkstyleMain -x checkstyleTest -x compileTestJava -FROM openjdk:22-jdk-slim +FROM openjdk:23-jdk-slim AS runtime WORKDIR /app COPY --from=builder /app/build/libs/*.jar app.jar diff --git a/backend/requests/blackjack.http b/backend/requests/blackjack.http deleted file mode 100644 index 21dd35e..0000000 --- a/backend/requests/blackjack.http +++ /dev/null @@ -1,20 +0,0 @@ -POST http://localhost:8080/blackjack/start -Authorization: Bearer {{token}} -Content-Type: application/json - -{ - "betAmount": 1.01 -} - -### -POST http://localhost:8080/blackjack/54/hit -Authorization: Bearer {{token}} - -### -POST http://localhost:8080/blackjack/202/stand -Authorization: Bearer {{token}} - -### -GET http://localhost:8080/blackjack/202 -Authorization: Bearer {{token}} - diff --git a/backend/requests/getBearerToken.http b/backend/requests/getBearerToken.http deleted file mode 100644 index 791c3a2..0000000 --- a/backend/requests/getBearerToken.http +++ /dev/null @@ -1,6 +0,0 @@ -POST http://localhost:9090/realms/LF12/protocol/openid-connect/token -Content-Type: application/x-www-form-urlencoded - -grant_type=password&client_id=lf12&username=lf12_test_user&password=secret&scope=openid - -> {% client.global.set("token", response.body.access_token); %} \ No newline at end of file diff --git a/backend/requests/healthCheck.http b/backend/requests/healthCheck.http deleted file mode 100644 index 4be46b5..0000000 --- a/backend/requests/healthCheck.http +++ /dev/null @@ -1 +0,0 @@ -GET localhost:8080/health \ No newline at end of file diff --git a/backend/requests/lootboxes.http b/backend/requests/lootboxes.http deleted file mode 100644 index 9059570..0000000 --- a/backend/requests/lootboxes.http +++ /dev/null @@ -1,9 +0,0 @@ -GET http://localhost:8080/lootboxes -Authorization: Bearer {{token}} -Content-Type: application/json - -### - -POST http://localhost:8080/lootboxes/2 -Authorization: Bearer {{token}} -Content-Type: application/json \ No newline at end of file diff --git a/backend/requests/user.http b/backend/requests/user.http deleted file mode 100644 index b594398..0000000 --- a/backend/requests/user.http +++ /dev/null @@ -1,27 +0,0 @@ -### Get User by ID -GET http://localhost:8080/user/52cc0208-a3bd-4367-94c5-0404b016a003 -Authorization: Bearer {{token}} - -### Get current user with token -GET http://localhost:8080/user -Authorization: Bearer {{token}} - -### Create User -POST http://localhost:8080/user -Content-Type: application/json -Authorization: Bearer {{token}} - -{ - "authentikId": "52cc0208-a3bd-4367-94c5-0404b016a003", - "username": "john.doe" -} - -### Deposit -POST http://localhost:8080/deposit/checkout -Content-Type: application/json -Origin: http://localhost:8080 -Authorization: Bearer {{token}} - -{ - "amount": 60.12 -} \ No newline at end of file diff --git a/backend/requests/webhook.http b/backend/requests/webhook.http deleted file mode 100644 index 8fa7f26..0000000 --- a/backend/requests/webhook.http +++ /dev/null @@ -1 +0,0 @@ -POST localhost:8080/webhook diff --git a/backend/src/main/java/de/szut/casino/CasinoApplication.java b/backend/src/main/java/de/szut/casino/CasinoApplication.java index 1d7e861..8c99f02 100644 --- a/backend/src/main/java/de/szut/casino/CasinoApplication.java +++ b/backend/src/main/java/de/szut/casino/CasinoApplication.java @@ -1,20 +1,10 @@ package de.szut.casino; -import de.szut.casino.lootboxes.LootBoxEntity; -import de.szut.casino.lootboxes.LootBoxRepository; -import de.szut.casino.lootboxes.RewardEntity; -import de.szut.casino.lootboxes.RewardRepository; -import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - @SpringBootApplication public class CasinoApplication { @@ -26,65 +16,4 @@ public class CasinoApplication { public static RestTemplate restTemplate() { return new RestTemplate(); } - - @Bean - public CommandLineRunner initData(LootBoxRepository lootBoxRepository, RewardRepository rewardRepository) { - return _ -> { - if (lootBoxRepository.count() == 0) { - LootBoxEntity basicLootBox = new LootBoxEntity(); - basicLootBox.setName("Basic LootBox"); - basicLootBox.setPrice(new BigDecimal("2")); - basicLootBox.setRewards(new ArrayList<>()); // Initialize the list - - LootBoxEntity premiumLootBox = new LootBoxEntity(); - premiumLootBox.setName("Premium LootBox"); - premiumLootBox.setPrice(new BigDecimal("5")); - premiumLootBox.setRewards(new ArrayList<>()); // Initialize the list - - lootBoxRepository.saveAll(Arrays.asList(basicLootBox, premiumLootBox)); - - RewardEntity commonReward = new RewardEntity(); - commonReward.setValue(new BigDecimal("0.50")); - commonReward.setProbability(new BigDecimal("0.7")); - - RewardEntity rareReward = new RewardEntity(); - rareReward.setValue(new BigDecimal("2.00")); - rareReward.setProbability(new BigDecimal("0.25")); - - RewardEntity epicReward = new RewardEntity(); - epicReward.setValue(new BigDecimal("5.00")); - epicReward.setProbability(new BigDecimal("0.5")); - - RewardEntity premiumCommon = new RewardEntity(); - premiumCommon.setValue(new BigDecimal("2.00")); - premiumCommon.setProbability(new BigDecimal("0.6")); - - RewardEntity premiumRare = new RewardEntity(); - premiumRare.setValue(new BigDecimal("5.00")); - premiumRare.setProbability(new BigDecimal("0.3")); - - RewardEntity legendaryReward = new RewardEntity(); - legendaryReward.setValue(new BigDecimal("15.00")); - legendaryReward.setProbability(new BigDecimal("0.10")); - - rewardRepository.saveAll(Arrays.asList( - commonReward, rareReward, epicReward, - premiumCommon, premiumRare, legendaryReward - )); - - basicLootBox.getRewards().add(commonReward); - basicLootBox.getRewards().add(premiumRare); - - premiumLootBox.getRewards().add(premiumCommon); - premiumLootBox.getRewards().add(premiumRare); - premiumLootBox.getRewards().add(legendaryReward); - - lootBoxRepository.saveAll(Arrays.asList(basicLootBox, premiumLootBox)); - - System.out.println("Initial LootBoxes and rewards created successfully"); - } else { - System.out.println("LootBoxes already exist, skipping initialization"); - } - }; - } } diff --git a/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameController.java b/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameController.java index c89f3ef..c9d5c26 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameController.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameController.java @@ -1,6 +1,7 @@ package de.szut.casino.blackjack; -import de.szut.casino.blackjack.dto.CreateBlackJackGameDto; +import de.szut.casino.shared.dto.BetDto; +import de.szut.casino.shared.service.BalanceService; import de.szut.casino.user.UserEntity; import de.szut.casino.user.UserService; import jakarta.validation.Valid; @@ -9,19 +10,18 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.math.BigDecimal; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.*; @Slf4j @RestController public class BlackJackGameController { + private final BalanceService balanceService; private final UserService userService; private final BlackJackService blackJackService; - public BlackJackGameController(UserService userService, BlackJackService blackJackService) { + public BlackJackGameController(BalanceService balanceService, UserService userService, BlackJackService blackJackService) { + this.balanceService = balanceService; this.blackJackService = blackJackService; this.userService = userService; } @@ -112,7 +112,7 @@ public class BlackJackGameController { } @PostMapping("/blackjack/start") - public ResponseEntity createBlackJackGame(@RequestBody @Valid CreateBlackJackGameDto createBlackJackGameDto, @RequestHeader("Authorization") String token) { + public ResponseEntity createBlackJackGame(@RequestBody @Valid BetDto betDto, @RequestHeader("Authorization") String token) { Optional optionalUser = userService.getCurrentUser(token); if (optionalUser.isEmpty()) { @@ -120,21 +120,11 @@ public class BlackJackGameController { } UserEntity user = optionalUser.get(); - BigDecimal balance = user.getBalance(); - BigDecimal betAmount = createBlackJackGameDto.getBetAmount(); - if (betAmount.compareTo(BigDecimal.ZERO) <= 0) { - Map errorResponse = new HashMap<>(); - errorResponse.put("error", "Invalid bet amount"); - return ResponseEntity.badRequest().body(errorResponse); + if (!this.balanceService.hasFunds(user, betDto)) { + return ResponseEntity.badRequest().body(Collections.singletonMap("error", "Insufficient funds")); } - if (betAmount.compareTo(balance) > 0) { - Map errorResponse = new HashMap<>(); - errorResponse.put("error", "Insufficient funds"); - return ResponseEntity.badRequest().body(errorResponse); - } - - return ResponseEntity.ok(blackJackService.createBlackJackGame(user, betAmount)); + return ResponseEntity.ok(blackJackService.createBlackJackGame(user, betDto.getBetAmount())); } } diff --git a/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameRepository.java b/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameRepository.java index d25a180..aafea4f 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameRepository.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackGameRepository.java @@ -1,12 +1,8 @@ package de.szut.casino.blackjack; -import de.szut.casino.user.UserEntity; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Service; -import java.util.Optional; - @Service public interface BlackJackGameRepository extends JpaRepository { } diff --git a/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java b/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java index 158e32c..cb31352 100644 --- a/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java +++ b/backend/src/main/java/de/szut/casino/blackjack/BlackJackService.java @@ -7,7 +7,6 @@ import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.List; -import java.util.Optional; import java.util.Random; @Service diff --git a/backend/src/main/java/de/szut/casino/blackjack/dto/CreateBlackJackGameDto.java b/backend/src/main/java/de/szut/casino/blackjack/dto/CreateBlackJackGameDto.java deleted file mode 100644 index e5b0c97..0000000 --- a/backend/src/main/java/de/szut/casino/blackjack/dto/CreateBlackJackGameDto.java +++ /dev/null @@ -1,16 +0,0 @@ -package de.szut.casino.blackjack.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.math.BigDecimal; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -public class CreateBlackJackGameDto { - private BigDecimal betAmount; -} diff --git a/backend/src/main/java/de/szut/casino/deposit/DepositController.java b/backend/src/main/java/de/szut/casino/deposit/DepositController.java index cdf883d..dbfe449 100644 --- a/backend/src/main/java/de/szut/casino/deposit/DepositController.java +++ b/backend/src/main/java/de/szut/casino/deposit/DepositController.java @@ -8,7 +8,6 @@ import de.szut.casino.deposit.dto.AmountDto; import de.szut.casino.deposit.dto.SessionIdDto; import de.szut.casino.user.UserEntity; import de.szut.casino.user.UserRepository; -import de.szut.casino.user.UserService; import de.szut.casino.user.dto.KeycloakUserDto; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Value; @@ -16,7 +15,10 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.Optional; diff --git a/backend/src/main/java/de/szut/casino/deposit/TransactionEntity.java b/backend/src/main/java/de/szut/casino/deposit/TransactionEntity.java index e3fc412..7c43af9 100644 --- a/backend/src/main/java/de/szut/casino/deposit/TransactionEntity.java +++ b/backend/src/main/java/de/szut/casino/deposit/TransactionEntity.java @@ -5,7 +5,7 @@ import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; -import java.math.BigDecimal; +import java.util.Date; @Setter @Getter @@ -26,4 +26,7 @@ public class TransactionEntity { @Enumerated(EnumType.STRING) private TransactionStatus status = TransactionStatus.PROCESSING; + + @Column(name = "created_at") + private Date createdAt = new Date(); } diff --git a/backend/src/main/java/de/szut/casino/deposit/TransactionRepository.java b/backend/src/main/java/de/szut/casino/deposit/TransactionRepository.java index 94b9b6b..5a16f0d 100644 --- a/backend/src/main/java/de/szut/casino/deposit/TransactionRepository.java +++ b/backend/src/main/java/de/szut/casino/deposit/TransactionRepository.java @@ -5,10 +5,20 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Service; +import java.util.List; import java.util.Optional; @Service public interface TransactionRepository extends JpaRepository { @Query("SELECT t FROM TransactionEntity t WHERE t.sessionId = ?1") Optional findOneBySessionID(String sessionId); + + @Query("SELECT t FROM TransactionEntity t WHERE t.user = ?1") + List findAllByUserId(UserEntity id); + + @Query("SELECT t FROM TransactionEntity t WHERE t.user = ?1 ORDER BY t.createdAt DESC LIMIT ?2 OFFSET ?3") + List findByUserIdWithLimit(UserEntity userEntity, Integer limit, Integer offset); + + @Query("SELECT COUNT(t) > ?2 + ?3 FROM TransactionEntity t WHERE t.user = ?1") + Boolean hasMore(UserEntity userEntity, Integer limit, Integer offset); } diff --git a/backend/src/main/java/de/szut/casino/deposit/TransactionService.java b/backend/src/main/java/de/szut/casino/deposit/TransactionService.java index b5ddfd2..fceb27b 100644 --- a/backend/src/main/java/de/szut/casino/deposit/TransactionService.java +++ b/backend/src/main/java/de/szut/casino/deposit/TransactionService.java @@ -7,7 +7,7 @@ import de.szut.casino.user.UserEntity; import de.szut.casino.user.UserRepository; import org.springframework.stereotype.Service; -import java.util.Objects; +import java.math.BigDecimal; import java.util.Optional; @Service @@ -55,7 +55,7 @@ public class TransactionService { UserEntity user = transaction.getUser(); Long amountTotal = checkoutSession.getAmountTotal(); if (amountTotal != null) { - user.addBalance(amountTotal); + user.addBalance(BigDecimal.valueOf(amountTotal).movePointLeft(2)); } userRepository.save(user); diff --git a/backend/src/main/java/de/szut/casino/deposit/WebhookController.java b/backend/src/main/java/de/szut/casino/deposit/WebhookController.java index be90a48..dba9041 100644 --- a/backend/src/main/java/de/szut/casino/deposit/WebhookController.java +++ b/backend/src/main/java/de/szut/casino/deposit/WebhookController.java @@ -1,26 +1,21 @@ package de.szut.casino.deposit; -import com.fasterxml.jackson.core.JsonProcessingException; - import com.stripe.Stripe; -import com.stripe.exception.SignatureVerificationException; import com.stripe.exception.StripeException; -import com.stripe.model.*; +import com.stripe.model.Event; import com.stripe.model.checkout.Session; import com.stripe.net.Webhook; -import com.stripe.param.checkout.SessionRetrieveParams; -import de.szut.casino.user.UserEntity; -import de.szut.casino.user.UserRepository; import jakarta.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; -import java.math.BigDecimal; import java.util.Objects; -import java.util.Optional; @RestController public class WebhookController { diff --git a/backend/src/main/java/de/szut/casino/lootboxes/CreateLootBoxDto.java b/backend/src/main/java/de/szut/casino/lootboxes/CreateLootBoxDto.java new file mode 100644 index 0000000..10e1b4e --- /dev/null +++ b/backend/src/main/java/de/szut/casino/lootboxes/CreateLootBoxDto.java @@ -0,0 +1,30 @@ +package de.szut.casino.lootboxes; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class CreateLootBoxDto { + @NotEmpty(message = "Loot box name cannot be empty") + @Size(min = 3, max = 50, message = "Loot box name must be between 3 and 50 characters") + private String name; + + @NotNull(message = "Price cannot be null") + @DecimalMin(value = "0.01", message = "Price must be greater than 0") + private BigDecimal price; + + private List rewards = new ArrayList<>(); +} diff --git a/backend/src/main/java/de/szut/casino/lootboxes/CreateRewardDto.java b/backend/src/main/java/de/szut/casino/lootboxes/CreateRewardDto.java new file mode 100644 index 0000000..0dbe819 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/lootboxes/CreateRewardDto.java @@ -0,0 +1,26 @@ +package de.szut.casino.lootboxes; + +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.math.BigDecimal; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class CreateRewardDto { + @NotNull(message = "Reward value cannot be null") + @DecimalMin(value = "0.00", message = "Reward value must be positive") + private BigDecimal value; + + @NotNull(message = "Probability cannot be null") + @DecimalMin(value = "0.0", message = "Probability must be at least 0.0") + @DecimalMax(value = "1.0", message = "Probability must be at most 1.0") + private BigDecimal probability; +} diff --git a/backend/src/main/java/de/szut/casino/lootboxes/LootBoxController.java b/backend/src/main/java/de/szut/casino/lootboxes/LootBoxController.java index 51a17e1..2d4309d 100644 --- a/backend/src/main/java/de/szut/casino/lootboxes/LootBoxController.java +++ b/backend/src/main/java/de/szut/casino/lootboxes/LootBoxController.java @@ -3,13 +3,11 @@ package de.szut.casino.lootboxes; import de.szut.casino.user.UserEntity; import de.szut.casino.user.UserRepository; import de.szut.casino.user.UserService; +import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; @RestController public class LootBoxController { @@ -55,4 +53,37 @@ public class LootBoxController { return ResponseEntity.ok(reward); } + + @PostMapping("/lootboxes") + public ResponseEntity createLootbox(@RequestBody @Valid CreateLootBoxDto createLootBoxDto) { + List rewardEntities = new ArrayList<>(); + + for (CreateRewardDto createRewardDto : createLootBoxDto.getRewards()) { + rewardEntities.add(new RewardEntity(createRewardDto.getValue(), createRewardDto.getProbability())); + } + + LootBoxEntity lootBoxEntity = new LootBoxEntity( + createLootBoxDto.getName(), + createLootBoxDto.getPrice(), + rewardEntities + ); + + this.lootBoxRepository.save(lootBoxEntity); + + return ResponseEntity.ok(lootBoxEntity); + } + + @DeleteMapping("/lootboxes/{id}") + public ResponseEntity deleteLootbox(@PathVariable Long id) { + Optional optionalLootBox = lootBoxRepository.findById(id); + if (optionalLootBox.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + LootBoxEntity lootBox = optionalLootBox.get(); + lootBoxRepository.delete(lootBox); + + return ResponseEntity.ok(Collections.singletonMap("message", "successfully deleted lootbox")); + } + } diff --git a/backend/src/main/java/de/szut/casino/lootboxes/LootBoxEntity.java b/backend/src/main/java/de/szut/casino/lootboxes/LootBoxEntity.java index 49eb532..8a3e9a9 100644 --- a/backend/src/main/java/de/szut/casino/lootboxes/LootBoxEntity.java +++ b/backend/src/main/java/de/szut/casino/lootboxes/LootBoxEntity.java @@ -1,15 +1,9 @@ package de.szut.casino.lootboxes; -import com.fasterxml.jackson.annotation.JsonBackReference; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonManagedReference; -import de.szut.casino.blackjack.CardEntity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.hibernate.annotations.SQLRestriction; import java.math.BigDecimal; import java.util.ArrayList; @@ -18,9 +12,15 @@ import java.util.List; @Entity @Getter @Setter -@AllArgsConstructor @NoArgsConstructor public class LootBoxEntity { + + public LootBoxEntity(String name, BigDecimal price, List rewards) { + this.name = name; + this.price = price; + this.rewards = rewards; + } + @Id @GeneratedValue private Long id; diff --git a/backend/src/main/java/de/szut/casino/lootboxes/RewardEntity.java b/backend/src/main/java/de/szut/casino/lootboxes/RewardEntity.java index ce0c155..1abd2df 100644 --- a/backend/src/main/java/de/szut/casino/lootboxes/RewardEntity.java +++ b/backend/src/main/java/de/szut/casino/lootboxes/RewardEntity.java @@ -1,9 +1,9 @@ package de.szut.casino.lootboxes; import com.fasterxml.jackson.annotation.JsonBackReference; -import com.fasterxml.jackson.annotation.JsonManagedReference; import jakarta.persistence.*; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import java.math.BigDecimal; @@ -13,7 +13,14 @@ import java.util.List; @Getter @Setter @Entity +@NoArgsConstructor public class RewardEntity { + + public RewardEntity(BigDecimal value, BigDecimal probability) { + this.value = value; + this.probability = probability; + } + @Id @GeneratedValue private Long id; diff --git a/backend/src/main/java/de/szut/casino/shared/dto/BetDto.java b/backend/src/main/java/de/szut/casino/shared/dto/BetDto.java new file mode 100644 index 0000000..a910a03 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/shared/dto/BetDto.java @@ -0,0 +1,18 @@ +package de.szut.casino.shared.dto; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.math.BigDecimal; + +@Getter +@Setter +@AllArgsConstructor +public class BetDto { + @NotNull(message = "Bet amount cannot be null") + @Positive(message = "Bet amount must be positive") + private BigDecimal betAmount; +} diff --git a/backend/src/main/java/de/szut/casino/shared/service/BalanceService.java b/backend/src/main/java/de/szut/casino/shared/service/BalanceService.java new file mode 100644 index 0000000..40e6caa --- /dev/null +++ b/backend/src/main/java/de/szut/casino/shared/service/BalanceService.java @@ -0,0 +1,36 @@ +package de.szut.casino.shared.service; + +import de.szut.casino.shared.dto.BetDto; +import de.szut.casino.user.UserEntity; +import de.szut.casino.user.UserRepository; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; + +@Service +public class BalanceService { + private UserRepository userRepository; + + public BalanceService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public boolean hasFunds(UserEntity user, BetDto betDto) { + BigDecimal balance = user.getBalance(); + BigDecimal betAmount = betDto.getBetAmount(); + + return betAmount.compareTo(balance) <= 0; + } + + public void addFunds(UserEntity user, BigDecimal amount) { + user.addBalance(amount); + + this.userRepository.save(user); + } + + public void subtractFunds(UserEntity user, BigDecimal amount) { + user.subtractBalance(amount); + + this.userRepository.save(user); + } +} diff --git a/backend/src/main/java/de/szut/casino/slots/SlotController.java b/backend/src/main/java/de/szut/casino/slots/SlotController.java new file mode 100644 index 0000000..8f98b1d --- /dev/null +++ b/backend/src/main/java/de/szut/casino/slots/SlotController.java @@ -0,0 +1,50 @@ +package de.szut.casino.slots; + +import de.szut.casino.shared.dto.BetDto; +import de.szut.casino.shared.service.BalanceService; +import de.szut.casino.user.UserEntity; +import de.szut.casino.user.UserService; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collections; +import java.util.Optional; + +@RestController +public class SlotController { + private final UserService userService; + private final BalanceService balanceService; + private final SlotService slotService; + + public SlotController(UserService userService, BalanceService balanceService, SlotService slotService) { + this.userService = userService; + this.balanceService = balanceService; + this.slotService = slotService; + } + + @PostMapping("/slots/spin") + public ResponseEntity spinSlots(@RequestBody @Valid BetDto betDto, @RequestHeader("Authorization") String token) { + Optional optionalUser = userService.getCurrentUser(token); + + if (optionalUser.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + UserEntity user = optionalUser.get(); + + if (!this.balanceService.hasFunds(user, betDto)) { + return ResponseEntity.badRequest().body(Collections.singletonMap("error", "Insufficient funds")); + } + + SpinResult spinResult = this.slotService.spin( + betDto.getBetAmount(), + user + ); + + return ResponseEntity.ok(spinResult); + } +} diff --git a/backend/src/main/java/de/szut/casino/slots/SlotService.java b/backend/src/main/java/de/szut/casino/slots/SlotService.java new file mode 100644 index 0000000..39ebeab --- /dev/null +++ b/backend/src/main/java/de/szut/casino/slots/SlotService.java @@ -0,0 +1,139 @@ +package de.szut.casino.slots; + +import de.szut.casino.shared.service.BalanceService; +import de.szut.casino.user.UserEntity; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +@Service +public class SlotService { + private final int REEL_LENGTH = 32; + + // 98% RTP + private final int SEVEN_COUNT = 1; + private final int BAR_COUNT = 4; + private final int BELL_COUNT = 7; + private final int CHERRY_COUNT = 10; + private final int BLANK_COUNT = 10; + + private final Symbol SEVEN = new Symbol("seven", new BigDecimal("1000")); + private final Symbol BAR = new Symbol("bar", new BigDecimal("85")); + private final Symbol BELL = new Symbol("bell", new BigDecimal("40")); + private final Symbol CHERRY = new Symbol("cherry", new BigDecimal("10")); + private final Symbol BLANK = new Symbol("blank", new BigDecimal("0")); + + private final List firstReel; + private final List secondReel; + private final List thirdReel; + + private final Random random; + private final BalanceService balanceService; + + public SlotService(BalanceService balanceService) { + this.random = new Random(); + this.balanceService = balanceService; + + List reelStrip = createReelStrip(); + this.firstReel = shuffleReel(reelStrip); + this.secondReel = shuffleReel(reelStrip); + this.thirdReel = shuffleReel(reelStrip); + } + + public SpinResult spin(BigDecimal betAmount, UserEntity user) { + int index1 = this.random.nextInt(REEL_LENGTH); + int index2 = this.random.nextInt(REEL_LENGTH); + int index3 = this.random.nextInt(REEL_LENGTH); + + Symbol symbol1 = getSymbolAt(this.firstReel, index1); + Symbol symbol2 = getSymbolAt(this.secondReel, index2); + Symbol symbol3 = getSymbolAt(this.thirdReel, index3); + + boolean isWin = symbol1.equals(symbol2) && symbol1.equals(symbol3); + + SpinResult spinResult = processResult(betAmount, user, isWin, symbol1); + buildResultMatrix(spinResult, index1, index2, index3); + + return spinResult; + } + + private SpinResult processResult(BigDecimal betAmount, UserEntity user, boolean isWin, Symbol winSymbol) { + BigDecimal resultAmount; + String status; + + if (isWin) { + resultAmount = betAmount.multiply(winSymbol.getPayoutMultiplier()); + status = "win"; + this.balanceService.addFunds(user, resultAmount); + } else { + resultAmount = betAmount; + status = "lose"; + this.balanceService.subtractFunds(user, betAmount); + } + + SpinResult spinResult = new SpinResult(); + spinResult.setStatus(status); + spinResult.setAmount(resultAmount); + spinResult.setWin(isWin); + + return spinResult; + } + + private void buildResultMatrix(SpinResult spinResult, int index1, int index2, int index3) { + List> resultMatrix = new ArrayList<>(3); + + for (int i = 0; i < 3; i++) { + resultMatrix.add(new ArrayList<>(3)); + } + + resultMatrix.getFirst().add(getSymbolAt(this.firstReel, index1 - 1)); + resultMatrix.getFirst().add(getSymbolAt(this.secondReel, index2 - 1)); + resultMatrix.getFirst().add(getSymbolAt(this.thirdReel, index3 - 1)); + + resultMatrix.get(1).add(getSymbolAt(this.firstReel, index1)); + resultMatrix.get(1).add(getSymbolAt(this.secondReel, index2)); + resultMatrix.get(1).add(getSymbolAt(this.thirdReel, index3)); + + resultMatrix.getLast().add(getSymbolAt(this.firstReel, index1 + 1)); + resultMatrix.getLast().add(getSymbolAt(this.secondReel, index2 + 1)); + resultMatrix.getLast().add(getSymbolAt(this.thirdReel, index3 + 1)); + + spinResult.setResultMatrix(resultMatrix); + } + + private List shuffleReel(List reelStrip) { + Collections.shuffle(reelStrip, this.random); + + return reelStrip; + } + + private List createReelStrip() { + List reelStrip = new ArrayList<>(REEL_LENGTH); + addSymbolsToStrip(reelStrip, CHERRY, CHERRY_COUNT); + addSymbolsToStrip(reelStrip, BELL, BELL_COUNT); + addSymbolsToStrip(reelStrip, BAR, BAR_COUNT); + addSymbolsToStrip(reelStrip, SEVEN, SEVEN_COUNT); + addSymbolsToStrip(reelStrip, BLANK, BLANK_COUNT); + return reelStrip; + } + + private void addSymbolsToStrip(List strip, Symbol symbol, int count) { + for (int i = 0; i < count; i++) { + strip.add(symbol); + } + } + + private Symbol getSymbolAt(List reel, int index) { + int effectiveIndex = index % REEL_LENGTH; + + if (effectiveIndex < 0) { + effectiveIndex += REEL_LENGTH; + } + + return reel.get(effectiveIndex); + } +} diff --git a/backend/src/main/java/de/szut/casino/slots/SpinResult.java b/backend/src/main/java/de/szut/casino/slots/SpinResult.java new file mode 100644 index 0000000..6263e9b --- /dev/null +++ b/backend/src/main/java/de/szut/casino/slots/SpinResult.java @@ -0,0 +1,24 @@ +package de.szut.casino.slots; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.math.BigDecimal; +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +public class SpinResult { + public SpinResult(String status, BigDecimal amount, boolean isWin) { + this.status = status; + this.amount = amount; + this.isWin = isWin; + } + + private String status; + private BigDecimal amount; + private boolean isWin; + private List> resultMatrix; +} diff --git a/backend/src/main/java/de/szut/casino/slots/Symbol.java b/backend/src/main/java/de/szut/casino/slots/Symbol.java new file mode 100644 index 0000000..806d14b --- /dev/null +++ b/backend/src/main/java/de/szut/casino/slots/Symbol.java @@ -0,0 +1,35 @@ +package de.szut.casino.slots; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.math.BigDecimal; + +@Getter +@Setter +@AllArgsConstructor +public class Symbol { + private String name; + private BigDecimal payoutMultiplier; + + @Override + public boolean equals(Object other) { + if (!(other instanceof Symbol that)) { + return false; + } + + return this.name.equals(that.name) && this.payoutMultiplier.equals(that.payoutMultiplier); + } + + @Override + public int hashCode() { + int hashCode = 1; + + hashCode = hashCode * 37 + this.name.hashCode(); + hashCode = hashCode * 37 + this.payoutMultiplier.hashCode(); + + return hashCode; + } +} diff --git a/backend/src/main/java/de/szut/casino/user/UserController.java b/backend/src/main/java/de/szut/casino/user/UserController.java index 5daf7ef..c2ad0d0 100644 --- a/backend/src/main/java/de/szut/casino/user/UserController.java +++ b/backend/src/main/java/de/szut/casino/user/UserController.java @@ -1,19 +1,14 @@ package de.szut.casino.user; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -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.RequestHeader; -import org.springframework.web.bind.annotation.RestController; - import de.szut.casino.user.dto.CreateUserDto; import de.szut.casino.user.dto.GetUserDto; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; @Slf4j @RestController diff --git a/backend/src/main/java/de/szut/casino/user/UserEntity.java b/backend/src/main/java/de/szut/casino/user/UserEntity.java index 67fc1ae..03f4a34 100644 --- a/backend/src/main/java/de/szut/casino/user/UserEntity.java +++ b/backend/src/main/java/de/szut/casino/user/UserEntity.java @@ -31,13 +31,31 @@ public class UserEntity { this.balance = balance; } - public void addBalance(long amountInCents) { - BigDecimal amountToAdd = BigDecimal.valueOf(amountInCents).movePointLeft(2); + public void addBalance(BigDecimal amountToAdd) { + if (amountToAdd == null || amountToAdd.compareTo(BigDecimal.ZERO) <= 0) { + return; + } if (this.balance == null) { - this.balance = amountToAdd; - } else { - this.balance = this.balance.add(amountToAdd); + this.balance = BigDecimal.ZERO; } + + this.balance = this.balance.add(amountToAdd); + } + + public void subtractBalance(BigDecimal amountToSubtract) { + if (amountToSubtract == null || amountToSubtract.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Amount to subtract must be positive."); + } + + if (this.balance == null) { + this.balance = BigDecimal.ZERO; + } + + if (this.balance.compareTo(amountToSubtract) < 0) { + throw new IllegalStateException("Insufficient funds to subtract " + amountToSubtract); + } + + this.balance = this.balance.subtract(amountToSubtract); } } diff --git a/backend/src/main/java/de/szut/casino/user/transaction/GetTransactionService.java b/backend/src/main/java/de/szut/casino/user/transaction/GetTransactionService.java new file mode 100644 index 0000000..831c438 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/user/transaction/GetTransactionService.java @@ -0,0 +1,45 @@ +package de.szut.casino.user.transaction; + +import de.szut.casino.deposit.TransactionEntity; +import de.szut.casino.deposit.TransactionRepository; +import de.szut.casino.user.UserEntity; +import de.szut.casino.user.UserService; +import de.szut.casino.user.transaction.dto.GetTransactionDto; +import de.szut.casino.user.transaction.dto.UserTransactionsDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class GetTransactionService { + + @Autowired + private UserService userService; + + @Autowired + private TransactionRepository transactionRepository; + + public UserTransactionsDto getUserTransactionsDto(String authToken, Integer limit, Integer offset) { + Optional user = this.userService.getCurrentUser(authToken); + if (user.isPresent()) { + List transactionEntities = this.transactionRepository.findByUserIdWithLimit(user.get(), limit, offset); + Boolean hasMore = this.transactionRepository.hasMore(user.get(), limit, offset); + + return new UserTransactionsDto(mapTransactionsToDtos(transactionEntities), hasMore); + } + + return new UserTransactionsDto(List.of(), false); + } + + public List mapTransactionsToDtos(List transactions) { + return transactions.stream() + .map(transaction -> new GetTransactionDto( + transaction.getAmount(), + transaction.getStatus(), + transaction.getCreatedAt()) + ).toList(); + } +} + diff --git a/backend/src/main/java/de/szut/casino/user/transaction/TransactionController.java b/backend/src/main/java/de/szut/casino/user/transaction/TransactionController.java new file mode 100644 index 0000000..10061fa --- /dev/null +++ b/backend/src/main/java/de/szut/casino/user/transaction/TransactionController.java @@ -0,0 +1,28 @@ +package de.szut.casino.user.transaction; + +import de.szut.casino.user.transaction.dto.UserTransactionsDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TransactionController { + + @Autowired + private GetTransactionService transactionService; + + @GetMapping("/user/transactions") + public ResponseEntity getUserTransactions( + @RequestHeader("Authorization") String authToken, + @RequestParam(value = "limit", required = false) Integer limit, + @RequestParam(value = "offset", required = false) Integer offset + ) { + UserTransactionsDto transactionEntities = this.transactionService.getUserTransactionsDto(authToken, limit, offset); + + return ResponseEntity.ok(transactionEntities); + } +} + diff --git a/backend/src/main/java/de/szut/casino/user/transaction/dto/GetTransactionDto.java b/backend/src/main/java/de/szut/casino/user/transaction/dto/GetTransactionDto.java new file mode 100644 index 0000000..f37fbf4 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/user/transaction/dto/GetTransactionDto.java @@ -0,0 +1,16 @@ +package de.szut.casino.user.transaction.dto; + +import de.szut.casino.deposit.TransactionStatus; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +import java.util.Date; + +@AllArgsConstructor +@NoArgsConstructor +public class GetTransactionDto { + public double amount = 0; + public TransactionStatus status = TransactionStatus.PROCESSING; + public Date createdAt = new Date(); +} + diff --git a/backend/src/main/java/de/szut/casino/user/transaction/dto/UserTransactionsDto.java b/backend/src/main/java/de/szut/casino/user/transaction/dto/UserTransactionsDto.java new file mode 100644 index 0000000..54e116b --- /dev/null +++ b/backend/src/main/java/de/szut/casino/user/transaction/dto/UserTransactionsDto.java @@ -0,0 +1,12 @@ +package de.szut.casino.user.transaction.dto; + +import lombok.AllArgsConstructor; + +import java.util.List; + +@AllArgsConstructor +public class UserTransactionsDto { + public List transactions; + public Boolean hasMore; +} + diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 4c56a9d..bb8a4be 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1,17 +1,17 @@ -spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:5432/postgresdb -spring.datasource.username=postgres_user -spring.datasource.password=postgres_pass -server.port=8080 +spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:postgresdb} +spring.datasource.username=${DB_USER:postgres_user} +spring.datasource.password=${DB_PASS:postgres_pass} +server.port=${HTTP_PORT:8080} spring.jpa.hibernate.ddl-auto=update stripe.secret.key=${STRIPE_SECRET_KEY:sk_test_51QrePYIvCfqz7ANgqam8rEwWcMeKiLOof3j6SCMgu2sl4sESP45DJxca16mWcYo1sQaiBv32CMR6Z4AAAGQPCJo300ubuZKO8I} -stripe.webhook.secret=whsec_746b6a488665f6057118bdb4a2b32f4916f16c277109eeaed5e8f8e8b81b8c15 -app.frontend-host=http://localhost:4200 +stripe.webhook.secret=${STRIPE_WEBHOOK_SECRET:whsec_746b6a488665f6057118bdb4a2b32f4916f16c277109eeaed5e8f8e8b81b8c15} +app.frontend-host=${FE_URL:http://localhost:4200} -spring.application.name=lf12_starter +spring.application.name=casino #client registration configuration -spring.security.oauth2.client.registration.authentik.client-id=MDqjm1kcWKuZfqHJXjxwAV20i44aT7m4VhhTL3Nm -spring.security.oauth2.client.registration.authentik.client-secret=GY2F8te6iAVYt1TNAUVLzWZEXb6JoMNp6chbjqaXNq4gS5xTDL54HqBiAlV1jFKarN28LQ7FUsYX4SbwjfEhZhgeoKuBnZKjR9eiu7RawnGgxIK9ffvUfMkjRxnmiGI5 +spring.security.oauth2.client.registration.authentik.client-id=${AUTH_CLIENT_ID:MDqjm1kcWKuZfqHJXjxwAV20i44aT7m4VhhTL3Nm} +spring.security.oauth2.client.registration.authentik.client-secret=${AUTH_CLIENT_SECRET:GY2F8te6iAVYt1TNAUVLzWZEXb6JoMNp6chbjqaXNq4gS5xTDL54HqBiAlV1jFKarN28LQ7FUsYX4SbwjfEhZhgeoKuBnZKjR9eiu7RawnGgxIK9ffvUfMkjRxnmiGI5} spring.security.oauth2.client.registration.authentik.provider=authentik spring.security.oauth2.client.registration.authentik.client-name=Authentik spring.security.oauth2.client.registration.authentik.scope=openid,email,profile @@ -20,16 +20,16 @@ spring.security.oauth2.client.registration.authentik.authorization-grant-type=au spring.security.oauth2.client.registration.authentik.redirect-uri={baseUrl}/login/oauth2/code/{registrationId} # Provider settings -spring.security.oauth2.client.provider.authentik.issuer-uri=https://oauth.simonis.lol/application/o/casino-dev/ -spring.security.oauth2.client.provider.authentik.authorization-uri=https://oauth.simonis.lol/application/o/authorize/ -spring.security.oauth2.client.provider.authentik.token-uri=https://oauth.simonis.lol/application/o/token/ -spring.security.oauth2.client.provider.authentik.user-info-uri=https://oauth.simonis.lol/application/o/userinfo/ -spring.security.oauth2.client.provider.authentik.jwk-set-uri=https://oauth.simonis.lol/application/o/casino-dev/jwks/ -spring.security.oauth2.client.provider.authentik.user-name-attribute=preferred_username +spring.security.oauth2.client.provider.authentik.issuer-uri=${AUTH_PROVIDER_ISSUER:https://oauth.simonis.lol/application/o/casino-dev/} +spring.security.oauth2.client.provider.authentik.authorization-uri=${AUTH_PROVIDER_AUTHORIZE_URI:https://oauth.simonis.lol/application/o/authorize/} +spring.security.oauth2.client.provider.authentik.token-uri=${AUTH_PROVIDER_TOKEN_URI:https://oauth.simonis.lol/application/o/token/} +spring.security.oauth2.client.provider.authentik.user-info-uri=${AUTH_PROVIDER_USERINFO_URI:https://oauth.simonis.lol/application/o/userinfo/} +spring.security.oauth2.client.provider.authentik.jwk-set-uri=${AUTH_PROVIDER_JWKS_URI:https://oauth.simonis.lol/application/o/casino-dev/jwks/} +spring.security.oauth2.client.provider.authentik.user-name-attribute=${AUTH_PROVIDER_NAME_ATTR:preferred_username} # Resource server config -spring.security.oauth2.resourceserver.jwt.issuer-uri=https://oauth.simonis.lol/application/o/casino-dev/ -spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://oauth.simonis.lol/application/o/casino-dev/jwks/ +spring.security.oauth2.resourceserver.jwt.issuer-uri=${AUTH_JWT_ISSUER_URI:https://oauth.simonis.lol/application/o/casino-dev}/ +spring.security.oauth2.resourceserver.jwt.jwk-set-uri=${AUTH_JWT_JWT_SET_URI:https://oauth.simonis.lol/application/o/casino-dev/jwks/} #OIDC provider configuration: logging.level.org.springframework.security=DEBUG diff --git a/frontend/.docker/Dockerfile b/frontend/.docker/Dockerfile new file mode 100644 index 0000000..494c91f --- /dev/null +++ b/frontend/.docker/Dockerfile @@ -0,0 +1,23 @@ +FROM oven/bun:debian AS build +WORKDIR /app + +RUN apt-get update -y && apt-get install nodejs -y + +ENV NODE_ENV=production + +COPY package.json bun.lock ./ +RUN bun install --frozen-lockfile + +COPY . . +RUN bun run build + +FROM nginx:alpine AS production + +RUN rm /etc/nginx/conf.d/default.conf +COPY .docker/casino.conf /etc/nginx/templates/nginx.conf.template +COPY .docker/entrypoint.sh /docker-entrypoint.d/40-custom-config-env.sh + +COPY --from=build /app/dist/casino /usr/share/nginx/html + +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/.docker/casino.conf b/frontend/.docker/casino.conf new file mode 100644 index 0000000..40b9613 --- /dev/null +++ b/frontend/.docker/casino.conf @@ -0,0 +1,19 @@ +server { + listen 80; + root /usr/share/nginx/html/browser; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + add_header Cache-Control "no-cache"; + } + + location /backend/ { + proxy_pass http://${BACKEND_HOST}:${BACKEND_PORT}/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } +} diff --git a/frontend/.docker/entrypoint.sh b/frontend/.docker/entrypoint.sh new file mode 100755 index 0000000..3842b5a --- /dev/null +++ b/frontend/.docker/entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/sh +# Default values if not provided +: ${BACKEND_HOST:=localhost} +: ${BACKEND_PORT:=8080} + +envsubst '$BACKEND_HOST $BACKEND_PORT' < /etc/nginx/templates/nginx.conf.template > /etc/nginx/conf.d/default.conf +exec nginx -g 'daemon off;' diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..4e10341 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,15 @@ +node_modules +dist +.angular +.git +.github +.vscode +.idea +*.md +!README.md +.DS_Store +.env* +npm-debug.log* +yarn-debug.log* +yarn-error.log* +bun-debug.log* \ No newline at end of file diff --git a/frontend/angular.json b/frontend/angular.json index f7a1430..a9f9a84 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -13,7 +13,7 @@ "build": { "builder": "@angular-devkit/build-angular:application", "options": { - "outputPath": "dist/lf10-starter2024", + "outputPath": "dist/casino", "index": "src/index.html", "browser": "src/main.ts", "tsConfig": "tsconfig.app.json", diff --git a/frontend/bun.lock b/frontend/bun.lock index 4b54921..6f44b2c 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -46,7 +46,7 @@ "karma-jasmine-html-reporter": "~2.1.0", "prettier": "^3.4.2", "typescript": "~5.8.0", - "typescript-eslint": "8.29.1", + "typescript-eslint": "8.31.0", }, }, }, @@ -683,13 +683,13 @@ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.29.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.29.1", "@typescript-eslint/type-utils": "8.29.1", "@typescript-eslint/utils": "8.29.1", "@typescript-eslint/visitor-keys": "8.29.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.31.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/type-utils": "8.31.0", "@typescript-eslint/utils": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ=="], - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.29.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.29.1", "@typescript-eslint/types": "8.29.1", "@typescript-eslint/typescript-estree": "8.29.1", "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg=="], + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.31.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/types": "8.31.0", "@typescript-eslint/typescript-estree": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw=="], "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.29.0", "", { "dependencies": { "@typescript-eslint/types": "8.29.0", "@typescript-eslint/visitor-keys": "8.29.0" } }, "sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw=="], - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.29.1", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.29.1", "@typescript-eslint/utils": "8.29.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw=="], + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.31.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.31.0", "@typescript-eslint/utils": "8.31.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg=="], "@typescript-eslint/types": ["@typescript-eslint/types@8.29.0", "", {}, "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg=="], @@ -697,7 +697,7 @@ "@typescript-eslint/utils": ["@typescript-eslint/utils@8.29.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.29.0", "@typescript-eslint/types": "8.29.0", "@typescript-eslint/typescript-estree": "8.29.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.29.1", "", { "dependencies": { "@typescript-eslint/types": "8.29.1", "eslint-visitor-keys": "^4.2.0" } }, "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.31.0", "", { "dependencies": { "@typescript-eslint/types": "8.31.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ=="], "@vitejs/plugin-basic-ssl": ["@vitejs/plugin-basic-ssl@1.2.0", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" } }, "sha512-mkQnxTkcldAzIsomk1UuLfAu9n+kpQ3JbHcpCp7d2Oo6ITtji8pHS3QToOWjhPFvNQSnhlkAjmGbhv2QvwO/7Q=="], @@ -1779,7 +1779,7 @@ "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], - "typescript-eslint": ["typescript-eslint@8.29.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.29.1", "@typescript-eslint/parser": "8.29.1", "@typescript-eslint/utils": "8.29.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-f8cDkvndhbQMPcysk6CUSGBWV+g1utqdn71P5YKwMumVMOG/5k7cHq0KyG4O52nB0oKS4aN2Tp5+wB4APJGC+w=="], + "typescript-eslint": ["typescript-eslint@8.31.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.31.0", "@typescript-eslint/parser": "8.31.0", "@typescript-eslint/utils": "8.31.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-u+93F0sB0An8WEAPtwxVhFby573E8ckdjwUUQUj9QA4v8JAvgtoDdIyYR3XFwFHq2W1KJ1AurwJCO+w+Y1ixyQ=="], "ua-parser-js": ["ua-parser-js@0.7.40", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ=="], @@ -1973,27 +1973,27 @@ "@types/express/@types/express-serve-static-core": ["@types/express-serve-static-core@4.19.6", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.29.1", "", { "dependencies": { "@typescript-eslint/types": "8.29.1", "@typescript-eslint/visitor-keys": "8.29.1" } }, "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.31.0", "", { "dependencies": { "@typescript-eslint/types": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.0" } }, "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.29.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.29.1", "@typescript-eslint/types": "8.29.1", "@typescript-eslint/typescript-estree": "8.29.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.31.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/types": "8.31.0", "@typescript-eslint/typescript-estree": "8.31.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww=="], - "@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.29.1", "", { "dependencies": { "@typescript-eslint/types": "8.29.1", "@typescript-eslint/visitor-keys": "8.29.1" } }, "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA=="], + "@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.31.0", "", { "dependencies": { "@typescript-eslint/types": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.0" } }, "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw=="], - "@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.29.1", "", {}, "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ=="], + "@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.31.0", "", {}, "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ=="], - "@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.29.1", "", { "dependencies": { "@typescript-eslint/types": "8.29.1", "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g=="], + "@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.31.0", "", { "dependencies": { "@typescript-eslint/types": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ=="], "@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.29.0", "", { "dependencies": { "@typescript-eslint/types": "8.29.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg=="], - "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.29.1", "", { "dependencies": { "@typescript-eslint/types": "8.29.1", "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g=="], + "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.31.0", "", { "dependencies": { "@typescript-eslint/types": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ=="], - "@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.29.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.29.1", "@typescript-eslint/types": "8.29.1", "@typescript-eslint/typescript-estree": "8.29.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA=="], + "@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.31.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/types": "8.31.0", "@typescript-eslint/typescript-estree": "8.31.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww=="], "@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.29.0", "", { "dependencies": { "@typescript-eslint/types": "8.29.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.29.1", "", {}, "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ=="], + "@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.31.0", "", {}, "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ=="], "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], @@ -2157,7 +2157,7 @@ "tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], - "typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.29.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.29.1", "@typescript-eslint/types": "8.29.1", "@typescript-eslint/typescript-estree": "8.29.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA=="], + "typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.31.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/types": "8.31.0", "@typescript-eslint/typescript-estree": "8.31.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww=="], "webpack/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], @@ -2193,21 +2193,21 @@ "@tufjs/models/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.29.1", "", {}, "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.31.0", "", {}, "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.29.1", "", {}, "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.31.0", "", {}, "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ=="], - "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.29.1", "", { "dependencies": { "@typescript-eslint/types": "8.29.1", "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g=="], + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.31.0", "", { "dependencies": { "@typescript-eslint/types": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ=="], "@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.29.1", "", {}, "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ=="], + "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.31.0", "", {}, "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ=="], "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.29.1", "", { "dependencies": { "@typescript-eslint/types": "8.29.1", "@typescript-eslint/visitor-keys": "8.29.1" } }, "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA=="], + "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.31.0", "", { "dependencies": { "@typescript-eslint/types": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.0" } }, "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw=="], - "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.29.1", "", {}, "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ=="], + "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.31.0", "", {}, "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ=="], "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], @@ -2287,11 +2287,11 @@ "tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.29.1", "", { "dependencies": { "@typescript-eslint/types": "8.29.1", "@typescript-eslint/visitor-keys": "8.29.1" } }, "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA=="], + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.31.0", "", { "dependencies": { "@typescript-eslint/types": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.0" } }, "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw=="], - "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.29.1", "", {}, "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ=="], + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.31.0", "", {}, "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ=="], - "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.29.1", "", { "dependencies": { "@typescript-eslint/types": "8.29.1", "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g=="], + "typescript-eslint/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.31.0", "", { "dependencies": { "@typescript-eslint/types": "8.31.0", "@typescript-eslint/visitor-keys": "8.31.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ=="], "webpack-dev-server/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], diff --git a/frontend/docker/docker-compose.yml b/frontend/docker/docker-compose.yml deleted file mode 100644 index 2123d63..0000000 --- a/frontend/docker/docker-compose.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: '3' - -volumes: - employee_postgres_data: - driver: local - -services: - postgres-employee: - container_name: postgres_employee - image: postgres:17.4 - volumes: - - employee_postgres_data:/var/lib/postgresql/data - environment: - POSTGRES_DB: employee_db - POSTGRES_USER: employee - POSTGRES_PASSWORD: secret - ports: - - "5432:5432" - - employee: - container_name: employee - image: berndheidemann/employee-management-service:1.1.3 - # image: berndheidemann/employee-management-service_without_keycloak:1.1 - environment: - spring.datasource.url: jdbc:postgresql://postgres-employee:5432/employee_db - spring.datasource.username: employee - spring.datasource.password: secret - ports: - - "8089:8089" - depends_on: - - postgres-employee diff --git a/frontend/package.json b/frontend/package.json index 8156b75..dac95a0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -55,6 +55,6 @@ "karma-jasmine-html-reporter": "~2.1.0", "prettier": "^3.4.2", "typescript": "~5.8.0", - "typescript-eslint": "8.29.1" + "typescript-eslint": "8.31.0" } } diff --git a/frontend/public/blackjack.webp b/frontend/public/blackjack.webp index a791c14..e47c246 100644 Binary files a/frontend/public/blackjack.webp and b/frontend/public/blackjack.webp differ diff --git a/frontend/public/images/1-box.png b/frontend/public/images/1-box.png deleted file mode 100644 index b343dbd..0000000 Binary files a/frontend/public/images/1-box.png and /dev/null differ diff --git a/frontend/public/images/2-box.png b/frontend/public/images/2-box.png deleted file mode 100644 index 0ec4438..0000000 Binary files a/frontend/public/images/2-box.png and /dev/null differ diff --git a/frontend/public/images/3-box.png b/frontend/public/images/3-box.png deleted file mode 100644 index 87a8401..0000000 Binary files a/frontend/public/images/3-box.png and /dev/null differ diff --git a/frontend/public/liars-dice.webp b/frontend/public/liars-dice.webp index df1fd1c..0e1a05c 100644 Binary files a/frontend/public/liars-dice.webp and b/frontend/public/liars-dice.webp differ diff --git a/frontend/public/lootbox.webp b/frontend/public/lootbox.webp index 710deed..0750897 100644 Binary files a/frontend/public/lootbox.webp and b/frontend/public/lootbox.webp differ diff --git a/frontend/public/plinko.webp b/frontend/public/plinko.webp index c11370b..ada7958 100644 Binary files a/frontend/public/plinko.webp and b/frontend/public/plinko.webp differ diff --git a/frontend/public/poker.webp b/frontend/public/poker.webp index 9c60024..329f4da 100644 Binary files a/frontend/public/poker.webp and b/frontend/public/poker.webp differ diff --git a/frontend/public/slots.webp b/frontend/public/slots.webp index 5cf639a..12a8d8e 100644 Binary files a/frontend/public/slots.webp and b/frontend/public/slots.webp differ diff --git a/frontend/src/app/app.config.ts b/frontend/src/app/app.config.ts index 671a717..d6ac35a 100644 --- a/frontend/src/app/app.config.ts +++ b/frontend/src/app/app.config.ts @@ -6,7 +6,7 @@ import { routes } from './app.routes'; import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; import { OAuthStorage, provideOAuthClient } from 'angular-oauth2-oidc'; -import { httpInterceptor } from './shared/interceptor/http.interceptor'; +import { httpInterceptor } from '@shared/interceptor/http.interceptor'; export const appConfig: ApplicationConfig = { providers: [ diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 169e387..c536f8f 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -21,14 +21,4 @@ export const routes: Routes = [ loadComponent: () => import('./feature/game/blackjack/blackjack.component'), canActivate: [authGuard], }, - { - path: 'game/lootboxes', - loadComponent: () => import('./feature/lootboxes/lootbox-selection/lootbox-selection.component'), - canActivate: [authGuard], - }, - { - path: 'game/lootboxes/open/:id', - loadComponent: () => import('./feature/lootboxes/lootbox-opening/lootbox-opening.component'), - canActivate: [authGuard], - }, ]; diff --git a/frontend/src/app/feature/deposit/deposit.component.ts b/frontend/src/app/feature/deposit/deposit.component.ts index 834a2e4..8768bbe 100644 --- a/frontend/src/app/feature/deposit/deposit.component.ts +++ b/frontend/src/app/feature/deposit/deposit.component.ts @@ -1,18 +1,18 @@ import { + AfterViewInit, ChangeDetectionStrategy, + ChangeDetectorRef, Component, ElementRef, EventEmitter, inject, Input, + OnChanges, + OnDestroy, OnInit, Output, - ViewChild, - AfterViewInit, - OnDestroy, - OnChanges, SimpleChanges, - ChangeDetectorRef, + ViewChild, } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { loadStripe, Stripe } from '@stripe/stripe-js'; diff --git a/frontend/src/app/feature/game/blackjack/blackjack.component.ts b/frontend/src/app/feature/game/blackjack/blackjack.component.ts index 370b535..3e58e25 100644 --- a/frontend/src/app/feature/game/blackjack/blackjack.component.ts +++ b/frontend/src/app/feature/game/blackjack/blackjack.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, inject, signal, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Router } from '@angular/router'; import { PlayingCardComponent } from './components/playing-card/playing-card.component'; @@ -6,7 +6,7 @@ import { DealerHandComponent } from './components/dealer-hand/dealer-hand.compon import { PlayerHandComponent } from './components/player-hand/player-hand.component'; import { GameControlsComponent } from './components/game-controls/game-controls.component'; import { GameInfoComponent } from './components/game-info/game-info.component'; -import { Card, BlackjackGame } from '@blackjack/models/blackjack.model'; +import { BlackjackGame, Card } from '@blackjack/models/blackjack.model'; import { BlackjackService } from '@blackjack/services/blackjack.service'; import { HttpErrorResponse } from '@angular/common/http'; import { GameResultComponent } from '@blackjack/components/game-result/game-result.component'; diff --git a/frontend/src/app/feature/game/blackjack/components/animated-number/animated-number.component.ts b/frontend/src/app/feature/game/blackjack/components/animated-number/animated-number.component.ts index 25ea0e4..7d78871 100644 --- a/frontend/src/app/feature/game/blackjack/components/animated-number/animated-number.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/animated-number/animated-number.component.ts @@ -1,12 +1,12 @@ import { + AfterViewInit, ChangeDetectionStrategy, Component, + ElementRef, Input, OnChanges, SimpleChanges, - ElementRef, ViewChild, - AfterViewInit, } from '@angular/core'; import { CommonModule, CurrencyPipe } from '@angular/common'; import { CountUp } from 'countup.js'; diff --git a/frontend/src/app/feature/game/blackjack/components/game-info/game-info.component.ts b/frontend/src/app/feature/game/blackjack/components/game-info/game-info.component.ts index ecd1fad..644fb22 100644 --- a/frontend/src/app/feature/game/blackjack/components/game-info/game-info.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/game-info/game-info.component.ts @@ -5,8 +5,8 @@ import { Input, OnChanges, Output, - SimpleChanges, signal, + SimpleChanges, } from '@angular/core'; import { CommonModule, CurrencyPipe } from '@angular/common'; import { FormGroup, ReactiveFormsModule } from '@angular/forms'; diff --git a/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.ts b/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.ts index b841ee8..74d02e4 100644 --- a/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input, Output, EventEmitter } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { CommonModule, CurrencyPipe } from '@angular/common'; import { animate, style, transition, trigger } from '@angular/animations'; import { GameState } from '../../enum/gameState'; diff --git a/frontend/src/app/feature/game/blackjack/components/playing-card/playing-card.component.ts b/frontend/src/app/feature/game/blackjack/components/playing-card/playing-card.component.ts index 8ae8824..0fbbb5a 100644 --- a/frontend/src/app/feature/game/blackjack/components/playing-card/playing-card.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/playing-card/playing-card.component.ts @@ -1,9 +1,9 @@ import { + AfterViewInit, ChangeDetectionStrategy, Component, - Input, - AfterViewInit, ElementRef, + Input, OnChanges, SimpleChanges, } from '@angular/core'; diff --git a/frontend/src/app/feature/game/blackjack/services/blackjack.service.ts b/frontend/src/app/feature/game/blackjack/services/blackjack.service.ts index ca3f218..5e43dc7 100644 --- a/frontend/src/app/feature/game/blackjack/services/blackjack.service.ts +++ b/frontend/src/app/feature/game/blackjack/services/blackjack.service.ts @@ -1,6 +1,6 @@ -import { Injectable, inject } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { Observable, catchError } from 'rxjs'; +import { catchError, Observable } from 'rxjs'; import { BlackjackGame } from '@blackjack/models/blackjack.model'; @Injectable({ diff --git a/frontend/src/app/feature/home/home.component.html b/frontend/src/app/feature/home/home.component.html index b1bfd9b..dc8ee59 100644 --- a/frontend/src/app/feature/home/home.component.html +++ b/frontend/src/app/feature/home/home.component.html @@ -81,12 +81,16 @@ [isOpen]="isDepositModalOpen" (closeModalEmitter)="closeDepositModal()" > - - + @@ -100,11 +104,13 @@
-

{{ transaction.type }}

-

{{ transaction.date }}

+

{{ transaction.status }}

+

+ {{ transaction.createdAt | date: 'd.m.Y H:m' }} +

{{ transaction.amount | currency: 'EUR' }} diff --git a/frontend/src/app/feature/home/home.component.ts b/frontend/src/app/feature/home/home.component.ts index 7320cd0..be84450 100644 --- a/frontend/src/app/feature/home/home.component.ts +++ b/frontend/src/app/feature/home/home.component.ts @@ -1,22 +1,36 @@ -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { CurrencyPipe, NgFor } from '@angular/common'; +import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; +import { AsyncPipe, CurrencyPipe, DatePipe, NgFor } from '@angular/common'; import { DepositComponent } from '../deposit/deposit.component'; import { ActivatedRoute, Router } from '@angular/router'; import { ConfirmationComponent } from '@shared/components/confirmation/confirmation.component'; -import { Transaction } from 'app/model/Transaction'; import { NavbarComponent } from '@shared/components/navbar/navbar.component'; import { Game } from 'app/model/Game'; +import { Observable } from 'rxjs'; +import { TransactionService } from '@service/transaction.service'; +import format from 'ajv/dist/vocabularies/format'; +import { TransactionHistoryComponent } from '../transaction-history/transaction-history.component'; +import { TransactionData } from '../../model/TransactionData'; @Component({ selector: 'app-homepage', standalone: true, - imports: [NavbarComponent, CurrencyPipe, NgFor, DepositComponent, ConfirmationComponent], + imports: [ + NavbarComponent, + CurrencyPipe, + NgFor, + DepositComponent, + ConfirmationComponent, + AsyncPipe, + DatePipe, + TransactionHistoryComponent, + ], templateUrl: './home.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export default class HomeComponent implements OnInit { isDepositModalOpen = false; isDepositSuccessful = false; + isTransactionModalOpen = false; constructor( public route: ActivatedRoute, @@ -68,17 +82,19 @@ export default class HomeComponent implements OnInit { id: '6', name: 'Lootboxen', image: '/lootbox.webp', - route: '/game/lootboxes', + route: '/game/lootbox', }, ]; allGames: Game[] = [...this.featuredGames]; - recentTransactions: Transaction[] = []; + recentTransactionData: Observable = + inject(TransactionService).getUsersTransactions(5); openDepositModal() { this.isDepositModalOpen = true; } + closeDepositModal() { this.isDepositModalOpen = false; } @@ -86,11 +102,22 @@ export default class HomeComponent implements OnInit { openDepositConfirmationModal() { this.isDepositSuccessful = true; } + + openTransactionModal() { + this.isTransactionModalOpen = true; + } + closeDepositConfirmationModal() { this.isDepositSuccessful = false; } + closeTransactionModal() { + this.isTransactionModalOpen = false; + } + navigateToGame(route: string) { this.router.navigate([route]); } + + protected readonly format = format; } diff --git a/frontend/src/app/feature/landing/landing.component.ts b/frontend/src/app/feature/landing/landing.component.ts index 62fa25e..915547f 100644 --- a/frontend/src/app/feature/landing/landing.component.ts +++ b/frontend/src/app/feature/landing/landing.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, OnInit, OnDestroy } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { NgFor } from '@angular/common'; import { NavbarComponent } from '@shared/components/navbar/navbar.component'; diff --git a/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.css b/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.css deleted file mode 100644 index 28fd570..0000000 --- a/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.css +++ /dev/null @@ -1,236 +0,0 @@ -body { - background: linear-gradient(to bottom, #181c2a, #232c43); -} - -/* Color classes */ -.text-yellow-400 { - color: #facc15; -} -.text-purple-400 { - color: #a78bfa; -} -.text-blue-400 { - color: #60a5fa; -} - -.border-yellow-400 { - border-color: #facc15; -} -.border-purple-400 { - border-color: #a78bfa; -} -.border-blue-400 { - border-color: #60a5fa; -} - -/* Loader animation */ -.loader { - border: 4px solid rgba(255, 255, 255, 0.2); - border-radius: 50%; - border-top: 4px solid #facc15; - width: 40px; - height: 40px; - animation: spin 1s linear infinite; -} -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} - -/* Open button styling */ -.open-btn { - background: linear-gradient(90deg, #4338ca 0%, #8b5cf6 100%); - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); - transition: all 0.2s ease; -} -.open-btn:hover { - background: linear-gradient(90deg, #4f46e5 0%, #a78bfa 100%); - transform: translateY(-2px); - box-shadow: 0 6px 8px rgba(0, 0, 0, 0.3); -} - -/* CSGO-style case opening display */ -.case-container { - position: relative; - width: 100%; - background: rgba(26, 31, 48, 0.6); - border-radius: 8px; - box-shadow: 0 0 20px rgba(0, 0, 0, 0.3); - overflow: hidden; - margin-bottom: 20px; - display: flex; - justify-content: center; -} - -.case-indicator { - position: absolute; - top: 50%; - left: 50%; /* Back to center - we'll adjust the animation instead */ - transform: translate(-50%, -50%); /* Center precisely */ - width: 6px; - height: 100%; - background: #facc15; - box-shadow: - 0 0 10px #facc15, - 0 0 15px rgba(255, 255, 255, 0.5); - z-index: 3; - animation: indicator-pulse 1.5s ease-in-out 10s infinite alternate; -} - -@keyframes indicator-pulse { - 0% { - opacity: 0.6; - box-shadow: - 0 0 10px #facc15, - 0 0 15px rgba(255, 255, 255, 0.3); - } - 100% { - opacity: 1; - box-shadow: - 0 0 15px #facc15, - 0 0 20px rgba(255, 255, 255, 0.7); - } -} - -.case-items-container { - position: relative; - z-index: 1; - padding: 10px 5px; - margin: 0 auto; - width: 100%; - height: 150px; /* Fixed height for the horizontal row */ - overflow: hidden; /* Hide scrollbar */ - display: flex; - justify-content: center; /* Center the items container */ - align-items: center; -} - -.case-items { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - gap: 8px; - padding: 5px 0px; /* Remove horizontal padding */ - height: 100%; - width: 100%; - animation: slide-in 10s cubic-bezier(0.05, 0.82, 0.17, 1) forwards; - transform: translateX(3000%); /* Extremely far initial position for 200 items */ - position: relative; /* Ensure positioning context */ -} - -@keyframes slide-in { - 0% { - transform: translateX(2000%); - } - 90% { - transform: translateX(-16%); /* Single overshoot for dramatic effect */ - } - 100% { - transform: translateX(-8%); /* Centered position */ - } -} - -.case-item { - transition: all 0.2s ease; - padding: 2px; - animation: item-flash 0.3s ease-out forwards; - animation-play-state: paused; -} - -@keyframes item-flash { - 0% { - filter: brightness(1); - } - 50% { - filter: brightness(1.8); - } - 100% { - filter: brightness(1.2); - } -} - -.case-item-inner { - background: #232c43; - border: 2px solid #2d3748; - border-radius: 8px; - padding: 10px 5px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 65px; - height: 100%; -} - -.case-item-won { - z-index: 2; - animation: highlight-winner 1s ease-out 10s forwards; -} - -/* Specific ID for the winning item to ensure it's visible */ -#winning-item { - z-index: 5; /* Higher than indicator */ -} - -@keyframes highlight-winner { - 0% { - transform: scale(1); - filter: brightness(1); - } - 10% { - transform: scale(1.3); - filter: brightness(2); - } - 20% { - transform: scale(1.2); - filter: brightness(1.8); - } - 30% { - transform: scale(1.25); - filter: brightness(2); - } - 40% { - transform: scale(1.2); - filter: brightness(1.8); - } - 50% { - transform: scale(1.25); - filter: brightness(2); - } - 60% { - transform: scale(1.2); - filter: brightness(1.8); - } - 70% { - transform: scale(1.25); - filter: brightness(2); - } - 80% { - transform: scale(1.2); - filter: brightness(1.8); - } - 90% { - transform: scale(1.25); - filter: brightness(2); - } - 100% { - transform: scale(1.2); - filter: brightness(1.5); - } -} - -.amount { - font-size: 1rem; - font-weight: bold; - margin-bottom: 4px; -} - -.rarity { - font-size: 0.75rem; - opacity: 0.7; -} diff --git a/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.html b/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.html deleted file mode 100644 index c85802d..0000000 --- a/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.html +++ /dev/null @@ -1,136 +0,0 @@ - -
-
-
-
Lade Lootbox...
-
- - -

{{ lootbox.name }}

-
Preis: {{ lootbox.price | currency: 'EUR' }}
- - -
-

Mögliche Gewinne:

-
-
-
- {{ reward.value | currency: 'EUR' }} -
-
- Chance: {{ reward.probability * 100 | number: '1.0-0' }}% -
-
-
-
- - -
- -
- - -
-
-
Öffne Lootbox...
-
- - -
- -
-
Dein Gewinn:
-
- {{ wonReward?.value | currency: 'EUR' }} -
-
- - - - -
- -
- - -
-
- -
- -
-
-
{{ reward.value | currency: 'EUR' }}
-
{{ reward.probability * 100 | number: '1.0-0' }}%
-
-
- - -
-
-
-
- - -
- - -
-
-
-
diff --git a/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.ts b/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.ts deleted file mode 100644 index 39a23a9..0000000 --- a/frontend/src/app/feature/lootboxes/lootbox-opening/lootbox-opening.component.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { Component, ChangeDetectorRef } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { ActivatedRoute, Router } from '@angular/router'; -import { LootboxService } from '../services/lootbox.service'; -import { LootBox, Reward } from 'app/model/LootBox'; -import { NavbarComponent } from '@shared/components/navbar/navbar.component'; - -function shuffle(array: T[]): T[] { - const arr = array.slice(); - for (let i = arr.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [arr[i], arr[j]] = [arr[j], arr[i]]; - } - return arr; -} - -@Component({ - selector: 'app-lootbox-opening', - standalone: true, - imports: [CommonModule, NavbarComponent], - templateUrl: './lootbox-opening.component.html', - styleUrls: ['./lootbox-opening.component.css'], -}) -export default class LootboxOpeningComponent { - lootbox: LootBox | null = null; - isLoading = true; - error = ''; - - // UI State - isOpening = false; - isOpen = false; - wonReward: Reward | null = null; - prizeList: Reward[] = []; - - constructor( - private route: ActivatedRoute, - private router: Router, - private lootboxService: LootboxService, - private cdr: ChangeDetectorRef - ) { - const idParam = this.route.snapshot.paramMap.get('id'); - if (!idParam) { - this.error = 'Invalid lootbox ID'; - this.isLoading = false; - return; - } - const lootboxId = parseInt(idParam, 10); - this.lootboxService.getAllLootBoxes().subscribe({ - next: (lootboxes) => { - this.lootbox = lootboxes.find((box) => box.id === lootboxId) || null; - this.isLoading = false; - this.cdr.detectChanges(); - }, - error: () => { - this.error = 'Failed to load lootbox data'; - this.isLoading = false; - this.cdr.detectChanges(); - }, - }); - } - - openLootbox() { - if (!this.lootbox || this.isOpening) return; - this.isOpening = true; - this.isOpen = false; - this.wonReward = null; - this.prizeList = []; // Clear previous prizes - this.cdr.detectChanges(); - - // Short delay to ensure animation plays from the beginning - setTimeout(() => { - this.lootboxService.purchaseLootBox(this.lootbox!.id).subscribe({ - next: (reward) => { - this.wonReward = reward; - this.generateCasePrizes(reward); - this.isOpening = false; - this.isOpen = true; - this.cdr.detectChanges(); - }, - error: () => { - // Fallback if API fails - const rewards = this.lootbox!.rewards; - const fallback = rewards[Math.floor(Math.random() * rewards.length)]; - this.wonReward = fallback; - this.generateCasePrizes(fallback); - this.isOpening = false; - this.isOpen = true; - this.cdr.detectChanges(); - }, - }); - }, 100); - } - - generateCasePrizes(wonReward: Reward) { - if (!this.lootbox) return; - - // Create a case opening display with an extremely large number of prizes for a massive scrolling animation - const prizeCount = 200; // Set to 200 for an extremely long scrolling animation - const winningPosition = Math.floor(prizeCount / 2); // Position of the winning prize (middle) - - // Get possible rewards from the lootbox - const possibleRewards = this.lootbox.rewards; - - // Generate an array of random rewards - let items: Reward[] = []; - for (let i = 0; i < prizeCount; i++) { - // Special handling for the winning position - if (i === winningPosition) { - items.push(wonReward); - } else { - // For all other positions, choose a random reward - // Weight rarer items to appear less frequently - const randomReward = this.getWeightedRandomReward(possibleRewards); - items.push(randomReward); - } - } - - this.prizeList = items; - } - - getWeightedRandomReward(rewards: Reward[]): Reward { - // Create a weighted distribution based on probabilities - const totalProbability = rewards.reduce((sum, reward) => sum + reward.probability, 0); - const randomValue = Math.random() * totalProbability; - - let cumulativeProbability = 0; - for (const reward of rewards) { - cumulativeProbability += reward.probability; - if (randomValue <= cumulativeProbability) { - return { ...reward }; // Return a copy of the reward - } - } - - // Fallback, should never reach here - return { ...rewards[0] }; - } - - openAgain() { - this.isOpening = false; - this.isOpen = false; - this.wonReward = null; - this.prizeList = []; - this.cdr.detectChanges(); - } - - getBoxImage(id: number): string { - return `/images/${id}-box.png`; - } - - goBack(): void { - this.router.navigate(['/game/lootboxes']); - } - - isWonReward(reward: Reward): boolean { - if (!this.wonReward) { - return false; - } - - return ( - reward.id === this.wonReward.id && - reward.value === this.wonReward.value && - reward.probability === this.wonReward.probability - ); - } - - // Calculate the center position for better alignment - getCenterOffset(): string { - return '0px'; // No additional offset - using animation transform instead - } - - // Check if item is at center position (100th item) - isCenterItem(index: number): boolean { - return index === Math.floor(200 / 2); // Center item at index 100 (0-indexed) - } -} - diff --git a/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.css b/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.css deleted file mode 100644 index bbeb8b0..0000000 --- a/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.css +++ /dev/null @@ -1,22 +0,0 @@ -.loader { - border: 4px solid rgba(255, 255, 255, 0.3); - border-radius: 50%; - border-top: 4px solid #fff; - width: 40px; - height: 40px; - animation: spin 1s linear infinite; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -.card { - transition: transform 0.3s ease, box-shadow 0.3s ease; -} - -.card:hover { - transform: translateY(-5px); - box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); -} \ No newline at end of file diff --git a/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.html b/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.html deleted file mode 100644 index 070e084..0000000 --- a/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.html +++ /dev/null @@ -1,53 +0,0 @@ - -
-

Lootboxen

- -
isLoading: {{ isLoading }} | error: {{ error }} | lootboxes: {{ lootboxes?.length }}
- -
-
-
- -
- {{ error }} -
- -
-
-
- -
- {{ lootbox.price | currency:'EUR' }} -
-
- -
-

{{ lootbox.name }}

- -
-

Mögliche Gewinne:

-
    -
  • - {{ reward.value | currency:'EUR' }} - {{ formatProbability(reward.probability) }} -
  • -
-
- -
- -
-
- -
-
-

Fairness garantiert - Alle Ergebnisse werden transparent berechnet.

-
-
-
-
-
\ No newline at end of file diff --git a/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.ts b/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.ts deleted file mode 100644 index 1382b23..0000000 --- a/frontend/src/app/feature/lootboxes/lootbox-selection/lootbox-selection.component.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { NavbarComponent } from '@shared/components/navbar/navbar.component'; -import { LootboxService } from '../services/lootbox.service'; -import { LootBox } from 'app/model/LootBox'; -import { Router } from '@angular/router'; -import { timeout } from 'rxjs'; - -@Component({ - selector: 'app-lootbox-selection', - standalone: true, - imports: [CommonModule, NavbarComponent], - templateUrl: './lootbox-selection.component.html', - styleUrls: ['./lootbox-selection.component.css'] -}) -export default class LootboxSelectionComponent implements OnInit { - lootboxes: LootBox[] = []; - isLoading = true; - error = ''; - - // Fallback data in case the API call fails - fallbackLootboxes: LootBox[] = [ - { - id: 1, - name: "Basic LootBox", - price: 2.00, - rewards: [ - { - id: 1, - value: 0.50, - probability: 0.70 - }, - { - id: 5, - value: 5.00, - probability: 0.30 - } - ] - }, - { - id: 2, - name: "Premium LootBox", - price: 5.00, - rewards: [ - { - id: 4, - value: 2.00, - probability: 0.60 - }, - { - id: 5, - value: 5.00, - probability: 0.30 - }, - { - id: 6, - value: 15.00, - probability: 0.10 - } - ] - }, - { - id: 3, - name: "Legendäre LootBox", - price: 15.00, - rewards: [ - { - id: 4, - value: 2.00, - probability: 0.60 - }, - { - id: 5, - value: 5.00, - probability: 0.30 - }, - { - id: 6, - value: 15.00, - probability: 0.10 - } - ] - } - ]; - - constructor(private lootboxService: LootboxService, private router: Router, private cdr: ChangeDetectorRef) {} - - ngOnInit(): void { - this.loadLootboxes(); - } - - loadLootboxes(): void { - this.isLoading = true; - this.lootboxService.getAllLootBoxes().pipe( - timeout(5000) - ).subscribe({ - next: (data) => { - console.log('Received lootboxes:', data); - this.lootboxes = data; - this.isLoading = false; - this.cdr.detectChanges(); - }, - error: (err) => { - this.error = 'Konnte keine Verbindung zum Backend herstellen. Zeige Demo-Daten.'; - this.lootboxes = this.fallbackLootboxes; - this.isLoading = false; - this.cdr.detectChanges(); - console.error('Failed to load lootboxes:', err); - } - }); - } - - getBoxImage(id: number): string { - return `/images/${id}-box.png`; - } - - openLootbox(lootboxId: number): void { - this.router.navigate(['/game/lootboxes/open', lootboxId]); - } - - getRarityClass(probability: number): string { - if (probability <= 0.1) { - return 'text-yellow-400'; // Legendary - } else if (probability <= 0.3) { - return 'text-purple-400'; // Rare - } else { - return 'text-blue-400'; // Common - } - } - - formatProbability(probability: number): string { - return (probability * 100).toFixed(0) + '%'; - } -} \ No newline at end of file diff --git a/frontend/src/app/feature/lootboxes/services/lootbox.service.ts b/frontend/src/app/feature/lootboxes/services/lootbox.service.ts deleted file mode 100644 index 040b55d..0000000 --- a/frontend/src/app/feature/lootboxes/services/lootbox.service.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Injectable, inject } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { Observable, catchError } from 'rxjs'; -import { LootBox, Reward } from 'app/model/LootBox'; - -@Injectable({ - providedIn: 'root', -}) -export class LootboxService { - private http = inject(HttpClient); - - getAllLootBoxes(): Observable { - return this.http - .get('/backend/lootboxes', { responseType: 'json' }) - .pipe( - catchError((error) => { - console.error('Get lootboxes error:', error); - throw error; - }) - ); - } - - purchaseLootBox(lootBoxId: number): Observable { - return this.http - .post(`/backend/lootboxes/${lootBoxId}`, {}, { responseType: 'json' }) - .pipe( - catchError((error) => { - console.error('Purchase lootbox error:', error); - throw error; - }) - ); - } -} \ No newline at end of file diff --git a/frontend/src/app/feature/transaction-history/transaction-history.component.css b/frontend/src/app/feature/transaction-history/transaction-history.component.css new file mode 100644 index 0000000..c68e294 --- /dev/null +++ b/frontend/src/app/feature/transaction-history/transaction-history.component.css @@ -0,0 +1,8 @@ +button[disabled] { + cursor: not-allowed; + background-color: #077b58; + box-shadow: none; +} +button[disabled]:hover { + background-color: #077b58; +} diff --git a/frontend/src/app/feature/transaction-history/transaction-history.component.html b/frontend/src/app/feature/transaction-history/transaction-history.component.html new file mode 100644 index 0000000..974964c --- /dev/null +++ b/frontend/src/app/feature/transaction-history/transaction-history.component.html @@ -0,0 +1,60 @@ + diff --git a/frontend/src/app/feature/transaction-history/transaction-history.component.ts b/frontend/src/app/feature/transaction-history/transaction-history.component.ts new file mode 100644 index 0000000..0bef06d --- /dev/null +++ b/frontend/src/app/feature/transaction-history/transaction-history.component.ts @@ -0,0 +1,54 @@ +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + inject, + Input, + Output, +} from '@angular/core'; +import { TransactionService } from '@service/transaction.service'; +import { Observable } from 'rxjs'; +import { AsyncPipe, CurrencyPipe, DatePipe, NgForOf, NgIf } from '@angular/common'; +import { AnimatedNumberComponent } from '@blackjack/components/animated-number/animated-number.component'; +import { TransactionData } from '../../model/TransactionData'; + +const PER_PAGE = 5; + +@Component({ + standalone: true, + selector: 'app-transaction-history', + imports: [NgForOf, AsyncPipe, CurrencyPipe, DatePipe, AnimatedNumberComponent, NgIf], + templateUrl: './transaction-history.component.html', + styleUrl: './transaction-history.component.css', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TransactionHistoryComponent { + @Input() + isOpen = false; + @Output() + closeEventEmitter = new EventEmitter(); + + protected offset = 0; + + private transactionService: TransactionService = inject(TransactionService); + transactionData$: Observable = this.loadTransactions(); + + closeDialog() { + this.isOpen = false; + this.closeEventEmitter.emit(); + } + + forward() { + this.offset++; + this.transactionData$ = this.loadTransactions(); + } + + back() { + this.offset--; + this.transactionData$ = this.loadTransactions(); + } + + loadTransactions() { + return this.transactionService.getUsersTransactions(PER_PAGE, this.offset * PER_PAGE); + } +} diff --git a/frontend/src/app/model/LootBox.ts b/frontend/src/app/model/LootBox.ts deleted file mode 100644 index f1547b1..0000000 --- a/frontend/src/app/model/LootBox.ts +++ /dev/null @@ -1,12 +0,0 @@ -export interface Reward { - id: number; - value: number; - probability: number; -} - -export interface LootBox { - id: number; - name: string; - price: number; - rewards: Reward[]; -} \ No newline at end of file diff --git a/frontend/src/app/model/Transaction.ts b/frontend/src/app/model/Transaction.ts index 21277b4..d3bccf1 100644 --- a/frontend/src/app/model/Transaction.ts +++ b/frontend/src/app/model/Transaction.ts @@ -1,6 +1,5 @@ export interface Transaction { - id: string; - type: string; + status: string; amount: number; - date: string; + createdAt: string; } diff --git a/frontend/src/app/model/TransactionData.ts b/frontend/src/app/model/TransactionData.ts new file mode 100644 index 0000000..2da39bb --- /dev/null +++ b/frontend/src/app/model/TransactionData.ts @@ -0,0 +1,6 @@ +import { Transaction } from './Transaction'; + +export interface TransactionData { + transactions: Transaction[]; + hasMore: boolean; +} diff --git a/frontend/src/app/service/transaction.service.ts b/frontend/src/app/service/transaction.service.ts new file mode 100644 index 0000000..55aba0f --- /dev/null +++ b/frontend/src/app/service/transaction.service.ts @@ -0,0 +1,24 @@ +import { inject, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { TransactionData } from '../model/TransactionData'; + +@Injectable({ + providedIn: 'root', +}) +export class TransactionService { + private http: HttpClient = inject(HttpClient); + + public getUsersTransactions(limit: number | null = null, offset: number | null = null) { + const baseUrl = new URL(`${window.location.origin}/backend/user/transactions`); + + if (limit !== null) { + baseUrl.searchParams.append('limit', limit.toString()); + } + + if (offset !== null) { + baseUrl.searchParams.append('offset', offset.toString()); + } + + return this.http.get(`${baseUrl}`); + } +} diff --git a/frontend/src/app/shared/components/confirmation/confirmation.component.ts b/frontend/src/app/shared/components/confirmation/confirmation.component.ts index 8bc884a..9ce91ba 100644 --- a/frontend/src/app/shared/components/confirmation/confirmation.component.ts +++ b/frontend/src/app/shared/components/confirmation/confirmation.component.ts @@ -1,12 +1,12 @@ import { + AfterViewInit, Component, ElementRef, EventEmitter, Input, + OnDestroy, Output, ViewChild, - AfterViewInit, - OnDestroy, } from '@angular/core'; import { ModalAnimationService } from '@shared/services/modal-animation.service'; import gsap from 'gsap'; diff --git a/frontend/src/app/shared/components/footer/footer.component.ts b/frontend/src/app/shared/components/footer/footer.component.ts index 1c3b309..2cb56e1 100644 --- a/frontend/src/app/shared/components/footer/footer.component.ts +++ b/frontend/src/app/shared/components/footer/footer.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { faMoneyBillTransfer, faCreditCard, faWallet } from '@fortawesome/free-solid-svg-icons'; -import { faPaypal, faGooglePay, faApplePay } from '@fortawesome/free-brands-svg-icons'; +import { faCreditCard, faMoneyBillTransfer, faWallet } from '@fortawesome/free-solid-svg-icons'; +import { faApplePay, faGooglePay, faPaypal } from '@fortawesome/free-brands-svg-icons'; @Component({ selector: 'app-footer', diff --git a/frontend/src/app/shared/components/navbar/navbar.component.ts b/frontend/src/app/shared/components/navbar/navbar.component.ts index 6bbeeb9..6cc6f56 100644 --- a/frontend/src/app/shared/components/navbar/navbar.component.ts +++ b/frontend/src/app/shared/components/navbar/navbar.component.ts @@ -2,8 +2,8 @@ import { ChangeDetectionStrategy, Component, inject, - OnInit, OnDestroy, + OnInit, signal, } from '@angular/core'; import { RouterModule } from '@angular/router'; diff --git a/frontend/src/assets/images/lootboxes/common-box.png b/frontend/src/assets/images/lootboxes/common-box.png deleted file mode 100644 index 0519ecb..0000000 --- a/frontend/src/assets/images/lootboxes/common-box.png +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/assets/images/lootboxes/legendary-box.png b/frontend/src/assets/images/lootboxes/legendary-box.png deleted file mode 100644 index 0519ecb..0000000 --- a/frontend/src/assets/images/lootboxes/legendary-box.png +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/assets/images/lootboxes/rare-box.png b/frontend/src/assets/images/lootboxes/rare-box.png deleted file mode 100644 index 0519ecb..0000000 --- a/frontend/src/assets/images/lootboxes/rare-box.png +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file