Skip to content

Commit

Permalink
Merge pull request #68 from SWM-M3PRO/develop
Browse files Browse the repository at this point in the history
V1.0.4
  • Loading branch information
qjvk2880 authored Aug 22, 2024
2 parents 73afd10 + 9b4d182 commit 133eacd
Show file tree
Hide file tree
Showing 49 changed files with 1,413 additions and 84 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/cd_dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ jobs:
echo "APPLE_TEAM_ID=${{ secrets.APPLE_TEAM_ID }}" >> .env
echo "APPLE_KEY_ID=${{ secrets.APPLE_KEY_ID }}" >> .env
echo "APPLE_PRIVATE_KEY=${{ secrets.APPLE_PRIVATE_KEY }}" >> .env
echo "NEED_UPDATE_VERSION=${{ secrets.NEED_UPDATE_VERSION }}" >> .env
echo "RECOMMEND_UPDATE=${{ secrets.RECOMMEND_UPDATE }}" >> .env
echo "SPRING_PROFILES_ACTIVE=dev" >> .env
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/cd_prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ jobs:
echo "APPLE_TEAM_ID=${{ secrets.APPLE_TEAM_ID }}" >> .env
echo "APPLE_KEY_ID=${{ secrets.APPLE_KEY_ID }}" >> .env
echo "APPLE_PRIVATE_KEY=${{ secrets.APPLE_PRIVATE_KEY }}" >> .env
echo "NEED_UPDATE_VERSION=${{ secrets.NEED_UPDATE_VERSION_PROD }}" >> .env
echo "RECOMMEND_UPDATE=${{ secrets.RECOMMEND_UPDATE_VERSION_PROD }}" >> .env
- name: gradlew에 실행 권한 부여
run: chmod +x ./gradlew
Expand Down
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

## GroundFlip - 땅따먹기 게임 기반 걷기 앱
<img width="224" alt="image" src="https://github.com/user-attachments/assets/dc88a049-bcf5-4d15-92e6-7b54bd0c3136">

### 👀 프로젝트 소개
내가 가는 길이 내 것이 되는 즐거움, 그라운드 플립! <br>
운동이 재미가 없어 꾸준히 하기 어려웠다구요?

그라운드 플립과 함께 걸어봐요!

그라운드 플립은 땅따먹기 게임에서 아이디어를 얻어 현대인들의 운동 부족을 해결하기 위해 제작되었습니다. <br>
사용자는 지도 상에 존재하는 수많은 픽셀들을 걸어나가며 차지할 수 있습니다. <br>
실시간 랭킹, 그룹 기능을 제공합니다. <br>

---
### 개발 환경
- Java 17
- Spring Boot 3.3.0

---

### 기술 세부 스택
- Spring Boot

- Spring Data JPA
- Spring Data Redis
- Spring Security
그 외

---

### 📚 기능 소개 <br>
BE Architecture <br>
<img width="651" alt="image" src="https://github.com/user-attachments/assets/2336e475-1017-4d4c-bd47-fb3e17601cf7">

ERD <br>
<img width="469" alt="image" src="https://github.com/user-attachments/assets/40b7d1d0-30b6-461d-9cd6-d7b656a40c43">
62 changes: 47 additions & 15 deletions deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,56 @@
APP_NAME="ground_flip"
REPOSITORY=/home/ubuntu/ground_flip

echo "> Check the currently running container"
CONTAINER_ID=$(docker ps -aqf "name=$APP_NAME")
pwd
ls
if [ -z "$CONTAINER_ID" ];
then
echo "> No such container is running."
echo "> Build Docker image"
sudo docker build -t "$APP_NAME" "$REPOSITORY"

TARGET_PORT=0
CURRENT_PORT=$(sudo docker ps --filter "name=$APP_NAME" --format "{{.Ports}}" | cut -d: -f2 | cut -d- -f1)
echo "> CURRENT_PORT = $CURRENT_PORT"

if [ "$CURRENT_PORT" == "8081" ]; then
TARGET_PORT=8082
else
echo "> Stop and remove container: $CONTAINER_ID"
docker stop "$CONTAINER_ID"
docker rm "$CONTAINER_ID"
TARGET_PORT=8081
fi
echo "> TARGET_PORT = $TARGET_PORT"


NEW_CONTAINER_NAME="$APP_NAME-$TARGET_PORT"
OLD_CONTAINER_NAME="$APP_NAME-$CURRENT_PORT"

echo "> Run the Docker container on port $TARGET_PORT"
sudo docker run -d -p $TARGET_PORT:8080 --env-file /home/ubuntu/ground_flip/.env -e TZ=Asia/Seoul -v /home/ubuntu/logs:/logs --name "$NEW_CONTAINER_NAME" "$APP_NAME"

for cnt in {1..10} # 10번 실행
do
echo "check server start.."

RESPONSE=$(curl -s http://127.0.0.1:$TARGET_PORT/check)

if echo "$RESPONSE" | grep -q "success"; then
echo "Container Started"
break;
else
echo "server not start.."
fi

echo "wait 10 seconds" # 10 초간 대기
sleep 10
done

echo "> Update NGINX configuration to route traffic to the new container"
NGINX_CONF="/etc/nginx/nginx.conf"
sudo sed -i "s/$CURRENT_PORT/$TARGET_PORT/g" "$NGINX_CONF"

echo "> Reload NGINX to apply the new configuration"
sudo nginx -s reload

echo "> Remove Old Container"
sudo docker rm -f $OLD_CONTAINER_NAME

echo "> Remove previous Docker image"
docker rmi "$APP_NAME"
sudo docker image prune -f

echo "> Build Docker image"
docker build -t "$APP_NAME" "$REPOSITORY"
echo "> Deployment to port $TARGET_PORT completed successfully."

echo "> Run the Docker container"
docker run -d -p 8080:8080 --env-file /home/ubuntu/ground_flip/.env -e TZ=Asia/Seoul -v /home/ubuntu/logs:/logs --name "$APP_NAME" "$APP_NAME"
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ protected SecurityFilterChain configure(HttpSecurity httpSecurity) throws Except
.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/version").permitAll()
.requestMatchers("/v3/api-docs/**").permitAll()
.requestMatchers("/api/swagger-ui/**").permitAll()
.requestMatchers("/api/docs/**").permitAll()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.m3pro.groundflip.controller;

import java.util.List;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.m3pro.groundflip.domain.dto.Response;
import com.m3pro.groundflip.domain.dto.myplace.MyPlaceRequest;
import com.m3pro.groundflip.domain.dto.myplace.MyPlaceResponse;
import com.m3pro.groundflip.service.MyPlaceService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/myplace")
@Tag(name = "myplace", description = "즐겨찾기 API")
public class MyPlaceController {
private final MyPlaceService myPlaceService;

@Operation(summary = "사용자 즐겨찾기 등록", description = "장소의 좌표, 이름을 저장한다")
@PutMapping("")
public Response<?> putMyPlace(
@Parameter(description = "즐겨찾기 저장 userId", required = true)
@RequestBody MyPlaceRequest myPlaceRequest
) {
myPlaceService.putMyPlace(myPlaceRequest);
return Response.createSuccessWithNoData();
}

@Operation(summary = "사용자 즐겨찾기 get", description = "즐겨찾기 장소의 좌표, 이름을 가져온다")
@GetMapping("/{userId}")
public Response<List<MyPlaceResponse>> getMyPlace(
@Parameter(description = "찾고자 하는 userId", required = true)
@PathVariable Long userId
) {
return Response.createSuccess(myPlaceService.getMyPlace(userId));
}

@Operation(summary = "사용자 즐겨찾기 delete", description = "즐겨찾기 장소를 삭제한다")
@DeleteMapping("")
public Response<?> deleteMyPlace(
@Parameter(description = "지우고자 하는 userId", required = true)
@RequestBody MyPlaceRequest myPlaceRequest
) {
myPlaceService.deleteMyPlace(myPlaceRequest);
return Response.createSuccessWithNoData();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.m3pro.groundflip.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.m3pro.groundflip.domain.dto.Response;
import com.m3pro.groundflip.domain.dto.permission.PermissionRequest;
import com.m3pro.groundflip.domain.dto.permission.PermissionResponse;
import com.m3pro.groundflip.service.PermissionService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@RestController
@RequiredArgsConstructor
@Slf4j
@RequestMapping("/api/permission")
@Tag(name = "permission", description = "권한 동의 API")
@SecurityRequirement(name = "Authorization")
public class PermissionController {
private final PermissionService permissionService;

@Operation(summary = "유저가 동의한 권한 조회", description = "유저가 동의한 권한을 조회하여 응답한다.")
@GetMapping("/{userId}")
public Response<PermissionResponse> getPermission(@PathVariable("userId") Long userId) {
return Response.createSuccess(permissionService.getAllPermissions(userId));
}

@Operation(summary = "서비스 알림 권한 동의", description = "서비스 푸시 알림을 받을지 받지 않을지 선택하는 api 이다.")
@PutMapping("/service-notification")
public Response<?> updateServiceNotification(@RequestBody PermissionRequest permissionRequest) {
permissionService.updateServiceNotificationsPreference(permissionRequest);
return Response.createSuccessWithNoData();
}

@Operation(summary = "마케팅 알림 권한 동의", description = "마케팅 푸시 알림을 받을지 받지 않을지 선택하는 api 이다.")
@PutMapping("/marketing-notification")
public Response<?> updateMarketingNotification(@RequestBody PermissionRequest permissionRequest) {
permissionService.updateMarketingNotificationsPreference(permissionRequest);
return Response.createSuccessWithNoData();
}

}
13 changes: 13 additions & 0 deletions src/main/java/com/m3pro/groundflip/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -13,9 +14,11 @@
import org.springframework.web.multipart.MultipartFile;

import com.m3pro.groundflip.domain.dto.Response;
import com.m3pro.groundflip.domain.dto.user.FcmTokenRequest;
import com.m3pro.groundflip.domain.dto.user.UserDeleteRequest;
import com.m3pro.groundflip.domain.dto.user.UserInfoRequest;
import com.m3pro.groundflip.domain.dto.user.UserInfoResponse;
import com.m3pro.groundflip.service.FcmService;
import com.m3pro.groundflip.service.UserService;

import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -31,6 +34,7 @@
@SecurityRequirement(name = "Authorization")
public class UserController {
private final UserService userService;
private final FcmService fcmService;

@Operation(summary = "사용자 기본 정보 조회", description = "닉네임, id, 출생년도, 성별, 프로필 사진, 그룹이름, 그룹 id 를 조회 한다.")
@GetMapping("/{userId}")
Expand Down Expand Up @@ -62,4 +66,13 @@ public Response<?> putUserInfo(
userService.deleteUser(userId, userDeleteRequest);
return Response.createSuccessWithNoData();
}

@Operation(summary = "FCM 등록 토큰 등록", description = "푸시 알림을 위한 FCM 등록 토큰을 저장한다.")
@PostMapping("/fcm-token")
public Response<?> postFcmToken(
@RequestBody FcmTokenRequest fcmTokenRequest
) {
fcmService.registerFcmToken(fcmTokenRequest);
return Response.createSuccessWithNoData();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.m3pro.groundflip.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.m3pro.groundflip.domain.dto.Response;
import com.m3pro.groundflip.domain.dto.version.VersionResponse;
import com.m3pro.groundflip.service.VersionService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
@Tag(name = "version", description = "앱 버전 API")
public class VersionController {
private final VersionService versionService;

@Operation(summary = "버전 업데이트 필요 여부 확인", description = "현재 앱 버전 업데이트가 필요한지 판별")
@GetMapping("/version")
public Response<VersionResponse> getVersion(
@Parameter(description = "현재 버전", required = true)
@RequestParam String currentVersion
) {
return Response.createSuccess(versionService.getVersion(currentVersion));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.m3pro.groundflip.domain.dto.myplace;

import com.m3pro.groundflip.enums.Place;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(title = "사용자 즐겨찾기 정보 등록")
public class MyPlaceRequest {

@Schema(description = "유저 id", example = "1")
private Long userId;

@Schema(description = "즐겨찾기 장소 이름", example = "학교")
private Place placeName;

@Schema(description = "위도", example = "37.321147")
private double latitude;

@Schema(description = "경도", example = "127.093171")
private double longitude;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.m3pro.groundflip.domain.dto.myplace;

import org.locationtech.jts.geom.Point;

import com.m3pro.groundflip.domain.entity.MyPlace;
import com.m3pro.groundflip.enums.Place;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Schema(title = "사용자 즐겨찾기 정보 검색")
public class MyPlaceResponse {

@Schema(description = "즐겨찾기 id", example = "1")
private Long id;

@Schema(description = "즐겨찾기 장소 이름", example = "학교")
private Place placeName;

@Schema(description = "즐겨찾기 장소 좌표", example = "0xE6100000010100000...")
private Point placePoint;

public static MyPlaceResponse from(MyPlace myplace) {
return MyPlaceResponse.builder()
.id(myplace.getId())
.placeName(myplace.getPlaceName())
.placePoint(myplace.getPlacePoint())
.build();
}
}
Loading

0 comments on commit 133eacd

Please sign in to comment.