Skip to content

Commit 5faa4a4

Browse files
songyi00JunRain2
andauthored
푸시 발송 API 개발 및 디바이스 도메인 구현 (#15)
* feat: 디바이스 도메인 구현 및 푸시 발송 API 개발 * feat: 디바이스 도메인 구현 및 푸시 발송 API 개발 * fix: rollback * fix: build error * fix: 임시 주석 처리 * resolve conflict --------- Co-authored-by: JunRain <[email protected]> Co-authored-by: 이준우 <[email protected]>
1 parent f99797f commit 5faa4a4

File tree

34 files changed

+871
-51
lines changed

34 files changed

+871
-51
lines changed

.github/workflows/cd.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ jobs:
7979
NCP_CONTAINER_REGISTRY_API='${{ secrets.NCP_CONTAINER_REGISTRY_API }}' \
8080
JWT_SECRET='${{ secrets.JWT_SECRET }}' \
8181
IMAGE_TAG='${{ github.sha }}' \
82+
ENCODED_FIREBASE_ADMIN_SDK='${{ secrets.ENCODED_FIREBASE_ADMIN_SDK }}' \
8283
docker-compose up -d
8384
8485
sudo docker image prune -f

docker/docker-compose.prod.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ services:
1717
APPLE_CLIENT_ID: ${APPLE_CLIENT_ID}
1818
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
1919
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
20+
ENCODED_FIREBASE_ADMIN_SDK: ${ENCODED_FIREBASE_ADMIN_SDK}
2021
depends_on:
2122
- mysql
2223
- redis

docker/init.sql

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,31 +18,31 @@ CREATE TABLE IF NOT EXISTS member
1818

1919
CREATE TABLE IF NOT EXISTS gathering
2020
(
21-
id BIGINT AUTO_INCREMENT PRIMARY KEY,
22-
gathering_name VARCHAR(255) NOT NULL,
23-
member_id BIGINT NOT NULL,
24-
interval_days BIGINT NOT NULL,
25-
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
26-
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
27-
deleted_at TIMESTAMP NULL,
21+
id BIGINT AUTO_INCREMENT PRIMARY KEY,
22+
gathering_name VARCHAR(255) NOT NULL,
23+
member_id BIGINT NOT NULL,
24+
interval_days BIGINT NOT NULL,
25+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
26+
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
27+
deleted_at TIMESTAMP NULL,
2828

2929
INDEX idx_deleted_at (deleted_at),
3030

31-
CONSTRAINT fk_gathering_member FOREIGN KEY (member_id) REFERENCES member(id)
31+
CONSTRAINT fk_gathering_member FOREIGN KEY (member_id) REFERENCES member (id)
3232
);
3333

3434
CREATE TABLE IF NOT EXISTS gathering_member
3535
(
3636
id BIGINT AUTO_INCREMENT PRIMARY KEY,
37-
gathering_id BIGINT NOT NULL,
38-
member_id BIGINT NOT NULL,
39-
is_host BIT NOT NULL,
40-
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
41-
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
42-
deleted_at TIMESTAMP NULL,
43-
44-
CONSTRAINT fk_gathering_member_gathering_id FOREIGN KEY (gathering_id) REFERENCES gathering(id),
45-
CONSTRAINT fk_gathering_member_member_id FOREIGN KEY (member_id) REFERENCES member(id),
37+
gathering_id BIGINT NOT NULL,
38+
member_id BIGINT NOT NULL,
39+
is_host BIT NOT NULL,
40+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
41+
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
42+
deleted_at TIMESTAMP NULL,
43+
44+
CONSTRAINT fk_gathering_member_gathering_id FOREIGN KEY (gathering_id) REFERENCES gathering (id),
45+
CONSTRAINT fk_gathering_member_member_id FOREIGN KEY (member_id) REFERENCES member (id),
4646
INDEX idx_gathering_member (gathering_id, member_id),
4747
INDEX idx_deleted_at (deleted_at)
4848
);
@@ -57,8 +57,8 @@ CREATE TABLE IF NOT EXISTS invitation
5757
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
5858
deleted_at TIMESTAMP NULL,
5959

60-
CONSTRAINT fk_invitation_gathering_id FOREIGN KEY (gathering_id) REFERENCES gathering(id),
61-
CONSTRAINT fk_invitation_member_id FOREIGN KEY (member_id) REFERENCES member(id),
60+
CONSTRAINT fk_invitation_gathering_id FOREIGN KEY (gathering_id) REFERENCES gathering (id),
61+
CONSTRAINT fk_invitation_member_id FOREIGN KEY (member_id) REFERENCES member (id),
6262
INDEX idx_gathering_member (gathering_id, member_id),
6363
INDEX idx_deleted_at (deleted_at)
6464
);
@@ -83,7 +83,7 @@ CREATE TABLE IF NOT EXISTS tag
8383
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
8484
deleted_at TIMESTAMP NULL,
8585

86-
CONSTRAINT fk_tag_category FOREIGN KEY (category_id) REFERENCES category(id),
86+
CONSTRAINT fk_tag_category FOREIGN KEY (category_id) REFERENCES category (id),
8787
INDEX idx_deleted_at (deleted_at)
8888
);
8989

@@ -93,7 +93,26 @@ CREATE TABLE IF NOT EXISTS gathering_tag
9393
gathering_id BIGINT NOT NULL,
9494
tag_id BIGINT NOT NULL,
9595

96-
CONSTRAINT fk_gathering_tag_gathering FOREIGN KEY (gathering_id) REFERENCES gathering(id),
97-
CONSTRAINT fk_gathering_tag_tag FOREIGN KEY (tag_id) REFERENCES tag(id),
96+
CONSTRAINT fk_gathering_tag_gathering FOREIGN KEY (gathering_id) REFERENCES gathering (id),
97+
CONSTRAINT fk_gathering_tag_tag FOREIGN KEY (tag_id) REFERENCES tag (id),
9898
UNIQUE KEY uk_gathering_tag (gathering_id, tag_id)
99-
);
99+
);
100+
101+
CREATE TABLE IF NOT EXISTS device
102+
(
103+
id BIGINT AUTO_INCREMENT PRIMARY KEY,
104+
created_at TIMESTAMP NOT NULL,
105+
updated_at TIMESTAMP NOT NULL,
106+
deleted_at TIMESTAMP DEFAULT NULL,
107+
108+
device_id VARCHAR(255) NOT NULL,
109+
member_id BIGINT NOT NULL,
110+
is_active BOOLEAN DEFAULT NULL,
111+
device_token VARCHAR(255) NOT NULL,
112+
app_version VARCHAR(20),
113+
os_version VARCHAR(20),
114+
115+
INDEX idx_device_device_id (device_id),
116+
INDEX idx_device_member_id (member_id),
117+
INDEX idx_device_deleted_at (deleted_at)
118+
)

tuk-api/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ dependencies {
5353

5454
implementation("com.nimbusds:nimbus-jose-jwt:${properties["nimbusJwtVersion"]}")
5555

56+
// firebase
57+
implementation("com.google.firebase:firebase-admin:${properties["firebaseVersion"]}")
58+
5659
// test
5760
testImplementation("org.springframework.boot:spring-boot-starter-test")
5861
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")

tuk-api/src/main/kotlin/nexters/tuk/application/auth/AuthService.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,14 @@ class AuthService(
2323
val userInfo = userProvider.getSocialUser(command)
2424
val member = memberService.login(
2525
MemberCommand.Login(
26-
userInfo.socialType, userInfo.socialId
26+
socialType = userInfo.socialType,
27+
socialId = userInfo.socialId,
2728
)
2829
) ?: memberService.signUp(
2930
MemberCommand.SignUp(
30-
email = userInfo.email, socialType = userInfo.socialType, socialId = userInfo.socialId
31+
email = userInfo.email,
32+
socialType = userInfo.socialType,
33+
socialId = userInfo.socialId,
3134
)
3235
)
3336

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package nexters.tuk.application.device
2+
3+
import nexters.tuk.application.device.dto.request.DeviceCommand
4+
import nexters.tuk.application.device.dto.response.DeviceResponse
5+
import nexters.tuk.contract.BaseException
6+
import nexters.tuk.contract.ErrorType
7+
import nexters.tuk.domain.device.Device
8+
import nexters.tuk.domain.device.DeviceRepository
9+
import org.slf4j.LoggerFactory
10+
import org.springframework.stereotype.Service
11+
import org.springframework.transaction.annotation.Transactional
12+
13+
@Service
14+
@Transactional
15+
class DeviceService(
16+
private val deviceRepository: DeviceRepository,
17+
) {
18+
@Transactional
19+
fun updateDeviceToken(
20+
memberId: Long,
21+
command: DeviceCommand.UpdateDeviceToken,
22+
): DeviceResponse.UpdateDeviceToken {
23+
require(command.deviceInfo.deviceToken != null) { "디바이스 토큰은 필수 정보입니다." }
24+
25+
val device = deviceRepository.findByDeviceIdAndMemberId(
26+
deviceId = command.deviceInfo.deviceId,
27+
memberId = memberId
28+
)?.let { device ->
29+
device.updateDeviceToken(
30+
newDeviceToken = command.deviceInfo.deviceToken,
31+
newAppVersion = command.deviceInfo.appVersion,
32+
newOsVersion = command.deviceInfo.osVersion,
33+
)
34+
device
35+
} ?: deviceRepository.save(
36+
Device.new(
37+
deviceId = command.deviceInfo.deviceType,
38+
deviceToken = command.deviceInfo.deviceToken,
39+
appVersion = command.deviceInfo.appVersion,
40+
osVersion = command.deviceInfo.osVersion,
41+
memberId = memberId
42+
)
43+
)
44+
45+
return DeviceResponse.UpdateDeviceToken(
46+
memberId = device.memberId,
47+
deviceId = device.deviceId,
48+
deviceToken = device.deviceToken,
49+
updatedAt = device.updatedAt
50+
)
51+
}
52+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package nexters.tuk.application.device.dto.request
2+
3+
import nexters.tuk.contract.device.TukDeviceInfo
4+
5+
class DeviceCommand {
6+
data class UpdateDeviceToken(
7+
val deviceInfo: TukDeviceInfo,
8+
)
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package nexters.tuk.application.device.dto.response
2+
3+
import java.time.LocalDateTime
4+
5+
class DeviceResponse {
6+
data class UpdateDeviceToken(
7+
val memberId: Long,
8+
val deviceId: String,
9+
val deviceToken: String,
10+
val updatedAt: LocalDateTime,
11+
)
12+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package nexters.tuk.application.push
2+
3+
import nexters.tuk.application.push.dto.request.PushCommand
4+
import nexters.tuk.application.push.dto.response.PushResponse
5+
6+
interface PushSender {
7+
fun send(
8+
recipients: List<PushCommand.PushRecipient>,
9+
message: PushCommand.MessagePayload,
10+
): PushResponse.Push
11+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package nexters.tuk.application.push
2+
3+
import nexters.tuk.application.push.dto.request.PushCommand
4+
import nexters.tuk.application.push.dto.response.PushResponse
5+
import org.slf4j.LoggerFactory
6+
import org.springframework.stereotype.Service
7+
import org.springframework.transaction.annotation.Transactional
8+
9+
@Service
10+
class PushService(
11+
private val pushSender: PushSender,
12+
) {
13+
private val logger = LoggerFactory.getLogger(PushService::class.java)
14+
15+
@Transactional
16+
fun sendPush(command: PushCommand.Push): PushResponse.Push {
17+
logger.info("Sending bulk push notification. Recipients: ${command.recipients.size}")
18+
19+
return pushSender.send(
20+
recipients = command.recipients,
21+
message = command.message
22+
)
23+
}
24+
}

0 commit comments

Comments
 (0)