Skip to content

Commit

Permalink
Merge pull request #111 from playkuround/develop
Browse files Browse the repository at this point in the history
[release] version 2.0.7
  • Loading branch information
redcarrot1 authored Sep 12, 2024
2 parents 9814022 + 87e605b commit e081bbf
Show file tree
Hide file tree
Showing 194 changed files with 3,735 additions and 1,966 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/server2-cd.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Spring Code deploy(home)
name: Spring Code deploy 2

on:
workflow_dispatch:
Expand Down Expand Up @@ -53,11 +53,11 @@ jobs:
run: ./gradlew clean build

- name: Deliver JAR File
uses: appleboy/scp-action@v0.1.7
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SSH_HOST2 }}
username: ${{ secrets.SSH_USERNAME2 }}
password: ${{ secrets.SSH_PASSWORD2 }}
key: ${{ secrets.SSH_PRVATE_KEY2 }}
port: ${{ secrets.SSH_PORT2 }}
source: "build/libs/*.jar"
target: "source"
Expand All @@ -68,7 +68,7 @@ jobs:
with:
host: ${{ secrets.SSH_HOST2 }}
username: ${{ secrets.SSH_USERNAME2 }}
password: ${{ secrets.SSH_PASSWORD2 }}
key: ${{ secrets.SSH_PRVATE_KEY2 }}
port: ${{ secrets.SSH_PORT2 }}
script: |
SOURCE_DIR=source/build/libs
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,8 @@ application-prod.yml
application-local.yml

application-dev-log.properties
application-prod-log.properties
application-prod-log.properties

AuthEmailSendServiceDevImpl.java
AuthEmailVerifyServiceDevImpl.java
DebugEmail.java
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
<a href="https://play.google.com/store/apps/details?id=com.umc.playkuround">
<img src="https://img.shields.io/badge/android-%2334A853.svg?style=for-the-badge&logo=Android&logoColor=white" alt="Android"/>
</a>

<a href="https://apps.apple.com/kr/app/playkuround/id6664073073">
<img src="https://img.shields.io/badge/iOS-0D96F6.svg?style=for-the-badge&logo=appstore&logoColor=white" alt="appstore"/>
</a>
<a href="https://www.instagram.com/playkuround_">
<img src="https://img.shields.io/badge/instagram-%23E4405F.svg?style=for-the-badge&logo=Instagram&logoColor=white" alt="Instagram"/>
</a>

<a href="https://litt.ly/playkuround">
<img src="https://img.shields.io/badge/Linktree-%2343E55E.svg?style=for-the-badge&logo=Linktree&logoColor=white" alt="Linktree"/>
</a>
Expand All @@ -32,9 +33,9 @@
<img src="https://github.com/playkuround/playkuround-server/assets/51076814/a6d0e3f5-fe40-48fa-ac6f-7297ee7d7137" width="190"/> |

## Server Stack
- Java 17
- JDK 17
- Spring Boot 3.1.3
- AWS EC2
- AWS EC2, ELB
- AWS RDS
- AWS ElastiCache for Redis
- Prometheus, Grafana
17 changes: 12 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {
}

group = 'com.playkuround'
version = '2.0.6'
version = '2.0.7'

java {
sourceCompatibility = '17'
Expand All @@ -31,29 +31,36 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'javax.xml.bind:jaxb-api:2.3.1'
testImplementation 'org.springframework.boot:spring-boot-starter-test'

// Lombok
compileOnly 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'

// DB
runtimeOnly 'com.h2database:h2'
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'it.ozimov:embedded-redis:0.7.2'

// JWT
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'

// Security
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'

// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'

implementation 'it.ozimov:embedded-redis:0.7.2'

// BadWord Filtering
implementation 'io.github.vaneproject:badwordfiltering:1.0.0'

// Monitoring
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import com.playkuround.playkuroundserver.domain.adventure.api.response.AdventureSaveResponse;
import com.playkuround.playkuroundserver.domain.adventure.application.AdventureService;
import com.playkuround.playkuroundserver.domain.adventure.dto.AdventureSaveDto;
import com.playkuround.playkuroundserver.domain.badge.dto.NewlyRegisteredBadge;
import com.playkuround.playkuroundserver.domain.badge.domain.BadgeType;
import com.playkuround.playkuroundserver.domain.score.domain.ScoreType;
import com.playkuround.playkuroundserver.domain.score.exception.ScoreTypeNotMatchException;
import com.playkuround.playkuroundserver.global.common.response.ApiResponse;
Expand All @@ -19,18 +19,20 @@
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/adventures")
@Tag(name = "Adventure", description = "Adventure API")
@RequestMapping("api/adventures")
@Tag(name = "Adventure", description = "탐험하기 API")
public class AdventureApi {

private final AdventureService adventureService;

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
@Operation(summary = "탐험하기", description = "탐험 점수를 저장합니다. 새롭게 얻은 뱃지가 있을 시 반환됩니다. " +
"새로 추가된 뱃지는 DB에 자동 반영됩니다. scoreType은 별도 문서 참고")
@Operation(summary = "탐험하기", description = "탐험 점수를 저장합니다. 새롭게 얻은 배지가 있을 시 반환됩니다. " +
"새로 추가된 배지는 DB에 자동 반영됩니다. scoreType은 별도 문서 참고")
public ApiResponse<AdventureSaveResponse> saveAdventure(@AuthenticationPrincipal UserDetailsImpl userDetails,
@RequestBody @Valid AdventureSaveRequest request) {
ScoreType scoreType = ScoreType.fromString(request.getScoreType())
Expand All @@ -39,7 +41,8 @@ public ApiResponse<AdventureSaveResponse> saveAdventure(@AuthenticationPrincipal
AdventureSaveDto adventureSaveDto
= new AdventureSaveDto(userDetails.getUser(), request.getLandmarkId(), location, request.getScore(), scoreType);

NewlyRegisteredBadge newlyRegisteredBadge = adventureService.saveAdventure(adventureSaveDto);
List<BadgeType> newlyRegisteredBadge = adventureService.saveAdventure(adventureSaveDto);

return ApiUtils.success(AdventureSaveResponse.from(newlyRegisteredBadge));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.fasterxml.jackson.annotation.JsonProperty;
import com.playkuround.playkuroundserver.domain.badge.domain.BadgeType;
import com.playkuround.playkuroundserver.domain.badge.dto.NewlyRegisteredBadge;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
Expand All @@ -14,26 +13,25 @@ public class AdventureSaveResponse {

private final List<BadgeInfo> newBadges;

private AdventureSaveResponse(NewlyRegisteredBadge newlyRegisteredBadge) {
this.newBadges = newlyRegisteredBadge.getNewlyBadges().stream()
.map(badgeInfo -> BadgeType.valueOf(badgeInfo.name()))
.map(it -> new BadgeInfo(it.name(), it.getDescription()))
private AdventureSaveResponse(List<BadgeType> badgeTypes) {
this.newBadges = badgeTypes.stream()
.map(badgeType -> new BadgeInfo(badgeType.name(), badgeType.getDescription()))
.toList();
}

public static AdventureSaveResponse from(NewlyRegisteredBadge newlyRegisteredBadge) {
return new AdventureSaveResponse(newlyRegisteredBadge);
public static AdventureSaveResponse from(List<BadgeType> badgeTypes) {
return new AdventureSaveResponse(badgeTypes);
}

@Getter
@AllArgsConstructor
public static class BadgeInfo {
@JsonProperty("name")
@Schema(description = "뱃지 이름", example = "ATTENDANCE_7", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "배지 이름", example = "ATTENDANCE_7", requiredMode = Schema.RequiredMode.REQUIRED)
private String name;

@JsonProperty("description")
@Schema(description = "뱃지 설명", example = "7일 연속 출석", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "배지 설명", example = "7일 연속 출석", requiredMode = Schema.RequiredMode.REQUIRED)
private String description;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,25 @@
import com.playkuround.playkuroundserver.domain.adventure.dto.AdventureSaveDto;
import com.playkuround.playkuroundserver.domain.adventure.exception.InvalidLandmarkLocationException;
import com.playkuround.playkuroundserver.domain.badge.application.BadgeService;
import com.playkuround.playkuroundserver.domain.badge.dto.NewlyRegisteredBadge;
import com.playkuround.playkuroundserver.domain.badge.domain.BadgeType;
import com.playkuround.playkuroundserver.domain.common.DateTimeService;
import com.playkuround.playkuroundserver.domain.landmark.dao.LandmarkRepository;
import com.playkuround.playkuroundserver.domain.landmark.domain.Landmark;
import com.playkuround.playkuroundserver.domain.landmark.exception.LandmarkNotFoundException;
import com.playkuround.playkuroundserver.domain.score.application.TotalScoreService;
import com.playkuround.playkuroundserver.domain.score.domain.ScoreType;
import com.playkuround.playkuroundserver.domain.user.dao.UserRepository;
import com.playkuround.playkuroundserver.domain.user.domain.HighestScore;
import com.playkuround.playkuroundserver.domain.user.domain.User;
import com.playkuround.playkuroundserver.global.util.DateTimeUtils;
import com.playkuround.playkuroundserver.global.util.Location;
import com.playkuround.playkuroundserver.global.util.LocationDistanceUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

@Service
@RequiredArgsConstructor
Expand All @@ -31,33 +34,37 @@ public class AdventureService {
private final TotalScoreService totalScoreService;
private final LandmarkRepository landmarkRepository;
private final AdventureRepository adventureRepository;
private final DateTimeService dateTimeService;

@Transactional
public NewlyRegisteredBadge saveAdventure(AdventureSaveDto adventureSaveDto) {
public List<BadgeType> saveAdventure(AdventureSaveDto adventureSaveDto) {
Landmark landmark = landmarkRepository.findById(adventureSaveDto.landmarkId())
.orElseThrow(() -> new LandmarkNotFoundException(adventureSaveDto.landmarkId()));
validateLocation(landmark, adventureSaveDto.requestLocation());

User user = adventureSaveDto.user();
ScoreType scoreType = adventureSaveDto.scoreType();
long score = adventureSaveDto.score();

updateUserScore(user, adventureSaveDto.scoreType(), adventureSaveDto.score());
saveAdventure(user, landmark, adventureSaveDto.scoreType(), adventureSaveDto.score());
updateUserScore(user, scoreType, score);
saveAdventure(user, landmark, scoreType, score);
updateLandmarkHighestScore(user, landmark);

return badgeService.updateNewlyAdventureBadges(user, landmark);
}

private void validateLocation(Landmark landmark, Location location) {
Location locationOfLandmark = new Location(landmark.getLatitude(), landmark.getLongitude());
double distance = LocationDistanceUtils.distance(locationOfLandmark, location);
if (distance > landmark.getRecognitionRadius()) {
if (!landmark.isInRecognitionRadius(location)) {
throw new InvalidLandmarkLocationException();
}
}

private void updateUserScore(User user, ScoreType scoreType, long score) {
totalScoreService.incrementTotalScore(user, score);
user.getHighestScore().updateGameHighestScore(scoreType, score);
HighestScore highestScore = user.getHighestScore();
highestScore.updateGameHighestScore(scoreType, score);
userRepository.save(user);

totalScoreService.incrementTotalScore(user, score);
}

private void saveAdventure(User user, Landmark landmark, ScoreType scoreType, long score) {
Expand All @@ -66,8 +73,10 @@ private void saveAdventure(User user, Landmark landmark, ScoreType scoreType, lo
}

private void updateLandmarkHighestScore(User user, Landmark landmark) {
LocalDateTime monthStartDateTime = DateTimeUtils.getMonthStartDateTime();
long sumScore = adventureRepository.getSumScoreByUserAndLandmark(user, landmark, monthStartDateTime);
LocalDate now = dateTimeService.getLocalDateNow();
LocalDateTime monthStartDateTime = DateTimeUtils.getMonthStartDateTime(now);

long sumScore = adventureRepository.sumScoreByUserAndLandmarkAfter(user, landmark, monthStartDateTime);
landmark.updateFirstUser(user, sumScore);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.playkuround.playkuroundserver.domain.adventure.dao;

import com.playkuround.playkuroundserver.domain.adventure.domain.Adventure;
import com.playkuround.playkuroundserver.domain.adventure.dto.UserAndScore;
import com.playkuround.playkuroundserver.domain.landmark.domain.Landmark;
import com.playkuround.playkuroundserver.domain.score.dto.NicknameAndScore;
import com.playkuround.playkuroundserver.domain.score.dto.NicknameAndScoreAndBadgeType;
import com.playkuround.playkuroundserver.domain.score.dto.RankAndScore;
import com.playkuround.playkuroundserver.domain.user.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -15,32 +16,44 @@

public interface AdventureRepository extends JpaRepository<Adventure, Long> {

@Query(value =
"SELECT new com.playkuround.playkuroundserver.domain.score.dto.NicknameAndScore(a.user.nickname, cast(SUM(a.score) as integer)) " +
"FROM Adventure a " +
"where a.landmark.id=:landmark AND a.createdAt >= :from " +
"GROUP BY a.user.id " +
"ORDER BY SUM(a.score) DESC, a.user.nickname DESC " +
"LIMIT 100")
List<NicknameAndScore> findRankTop100DescByLandmarkId(@Param(value = "landmark") Long landmarkId, @Param(value = "from") LocalDateTime from);

@Query(value =
"SELECT new com.playkuround.playkuroundserver.domain.score.dto.RankAndScore(cast(user_rank as integer), cast(score as integer)) FROM " +
"(SELECT a.user.id as user_id, (RANK() over (order by SUM(a.score) desc)) as user_rank, SUM(a.score) as score " +
"FROM Adventure a " +
"where a.landmark.id=:landmark AND a.createdAt >= :from " +
"GROUP BY a.user.id) " +
"where user_id=:#{#user.id}")
@Query("SELECT new com.playkuround.playkuroundserver.domain.score.dto.NicknameAndScoreAndBadgeType(a.user.nickname, cast(SUM(a.score) as integer), a.user.profileBadge) " +
"FROM Adventure a " +
"where a.landmark.id=:landmark AND a.createdAt >= :from " +
"GROUP BY a.user.id " +
"ORDER BY SUM(a.score) DESC, a.user.nickname DESC " +
"LIMIT 100")
List<NicknameAndScoreAndBadgeType> findRankTop100DescByLandmarkId(@Param(value = "landmark") Long landmarkId, @Param(value = "from") LocalDateTime from);

@Query("SELECT new com.playkuround.playkuroundserver.domain.score.dto.RankAndScore(cast(user_rank as integer), cast(score as integer)) FROM " +
"(SELECT a.user.id as user_id, (RANK() over (order by SUM(a.score) desc)) as user_rank, SUM(a.score) as score " +
"FROM Adventure a " +
"where a.landmark.id=:landmark AND a.createdAt >= :from " +
"GROUP BY a.user.id) " +
"where user_id=:#{#user.id}")
Optional<RankAndScore> findMyRankByLandmarkId(@Param(value = "user") User user,
@Param(value = "landmark") Long landmarkId,
@Param(value = "from") LocalDateTime from);

@Query("SELECT SUM(a.score) " +
@Query("""
SELECT SUM(a.score)
FROM Adventure a
WHERE a.user.id=:#{#user.id} AND a.landmark.id=:#{#landmark.id} AND a.createdAt >= :from
""")
long sumScoreByUserAndLandmarkAfter(@Param(value = "user") User user,
@Param(value = "landmark") Landmark landmark,
@Param(value = "from") LocalDateTime from);

long countByUserAndLandmark(User user, Landmark landmark);

@Query("SELECT new com.playkuround.playkuroundserver.domain.adventure.dto.UserAndScore(a.user, cast(SUM(a.score) as long)) " +
"FROM Adventure a " +
"WHERE a.user.id=:#{#user.id} AND a.landmark.id=:#{#landmark.id} AND a.createdAt >= :from")
long getSumScoreByUserAndLandmark(@Param(value = "user") User user,
@Param(value = "landmark") Landmark landmark,
@Param(value = "from") LocalDateTime from);
"where a.landmark.id=:landmark AND a.createdAt >= :from " +
"GROUP BY a.user.id " +
"ORDER BY SUM(a.score) DESC, a.user.nickname DESC " +
"LIMIT :limit")
List<UserAndScore> findRankDescBy(@Param(value = "landmark") Long landmarkId,
@Param(value = "from") LocalDateTime from,
@Param(value = "limit") int limit);

long countByUserAndLandmark(User user, Landmark requestSaveLandmark);
void deleteByUser(User user);
}
Loading

0 comments on commit e081bbf

Please sign in to comment.