From 88d9dc1f84d35ba4078650a2ee742def1a720580 Mon Sep 17 00:00:00 2001 From: Taewan Park Date: Thu, 14 Dec 2023 19:23:35 +0900 Subject: [PATCH] v1.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit πŸš€ Feature Updates - 앱이 Foreground에 μžˆμ–΄λ„ μ•Œλ¦Όμ΄ μ˜€κ²Œλ” 지원 πŸ”§ Bug Fixes - λͺ©ν‘œ 가격 μ„€μ • λ²”μœ„κ°€ 0~999999999 μ‚¬μ΄μ—μ„œ μ„€μ •λ˜λ„λ‘ μˆ˜μ • - μƒν’ˆ 상세 νŽ˜μ΄μ§€ 제λͺ© μ΅œλŒ€ 쀄 수λ₯Ό 3μ€„λ‘œ λ³€κ²½ Co-Authored-By: EunhoKang Co-Authored-By: ootr47 <83055885+ootr47@users.noreply.github.com> Co-Authored-By: 손문기 <39684860+Muungi@users.noreply.github.com> Co-Authored-By: ByeongIk Choi --- README.md | 126 +++++++++++++++++- android/app/build.gradle.kts | 4 +- .../PriceGuardFirebaseMessagingService.kt | 100 ++++++++++++++ .../setprice/SetTargetPriceFragment.kt | 19 +-- .../java/app/priceguard/ui/util/Dialog.kt | 1 - .../ui/{ => util}/ErrorDialogFragment.kt | 2 +- .../src/main/res/layout/activity_detail.xml | 2 +- android/app/src/main/res/values/strings.xml | 3 + android/release_notes.txt | 15 ++- backend/src/cache/cache.service.ts | 4 + backend/src/cache/tracking.cache.ts | 17 ++- backend/src/constants.ts | 1 + backend/src/dto/product.add.dto.ts | 4 +- backend/src/product/product.service.ts | 5 +- 14 files changed, 273 insertions(+), 30 deletions(-) rename android/app/src/main/java/app/priceguard/ui/{ => util}/ErrorDialogFragment.kt (98%) diff --git a/README.md b/README.md index ea347fd..8196f9c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Price Guard +# PriceGuard priceguard_icon_web ## πŸ›‘οΈ ν”„λ‘œμ νŠΈ μ†Œκ°œ @@ -9,8 +9,117 @@ _μ›ν•˜λŠ” μƒν’ˆμ˜ 가격을 μΆ”μ ν•˜κ³  μ €λ ΄ν• λ•Œ κ΅¬μž…ν•˜μ„Έμš”!_ PriceGuardλŠ” κ΅­λ‚΄ μƒκ±°λž˜ μ‚¬μ΄νŠΈλ“€μ˜ μƒν’ˆ 가격을 μΆ”μ ν•©λ‹ˆλ‹€. μ‚¬μš©μžκ°€ μ›ν•˜λŠ” μƒν’ˆ 링크λ₯Ό λ“±λ‘ν•˜λ©΄ ν•΄λ‹Ή μƒν’ˆλ“€μ˜ 정보λ₯Ό 주기적으둜 ν™•μΈν•©λ‹ˆλ‹€. μ‚¬μš©μžκ°€ λͺ©ν‘œ 가격을 μ„€μ •ν•΄μ„œ λͺ©ν‘œ 가격 μ΄ν•˜μΈ μƒν’ˆμ΄ 있으면 μ•Œλ¦Όμ„ λ³΄λƒ…λ‹ˆλ‹€. +λ˜ν•œ μ•± λ‚΄μ—μ„œ μ›ν•˜λŠ” μƒν’ˆμ˜ 가격 λ³€ν™”λ₯Ό κ·Έλž˜ν”„λ‘œ ν‘œν˜„ν•©λ‹ˆλ‹€. ``` +μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ€ [링크](https://appdistribution.firebase.google.com/pub/i/b299ae01bd67c829)λ₯Ό 톡해 받을 수 μžˆμŠ΅λ‹ˆλ‹€. + +## πŸ₯… 기술적 도전 + +우리 νŒ€μ€ 이 ν”„λ‘œμ νŠΈμ—μ„œ λ‹€μŒκ³Ό 같은 기술적 도전 과제λ₯Ό ν•΄κ²°ν•˜λ € ν•©λ‹ˆλ‹€. + +1. κ·Έλž˜ν”„ μž‘μ„±μ„ μœ„ν•œ λŒ€μš©λŸ‰μ˜ μ‹œκ³„μ—΄ 데이터λ₯Ό μ •μ˜ν•˜κ³  이λ₯Ό 효율적으둜 μ²˜λ¦¬ν•˜λŠ” 방법을 κ³ μ•ˆν•œλ‹€. +2. λ‹€μˆ˜μ˜ μ‚¬μš©μžμ— λŒ€ν•΄ 짧은 주기둜 데이터λ₯Ό μˆ˜μ§‘ν•΄μ•Όν•˜λŠ” μ‹œλ‚˜λ¦¬μ˜€μ— λŒ€ν•œ 효율적인 해결책을 μ°ΎλŠ”λ‹€. +3. κ°œλ°œν•œ κΈ°λŠ₯을 보닀 일반적인 상황에 μ“Έ 수 μžˆλ„λ‘ λΌμ΄λΈŒλŸ¬λ¦¬ν™”ν•œλ‹€. + +이와 같은 기술적 도전을 ν•΄κ²°ν•˜λŠ” 과정은 μ•„λž˜ λ§ν¬μ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. + +1. [κ·Έλž˜ν”„ 라이브러리 κ΅¬ν˜„](https://github.com/boostcampwm2023/and09-PriceGuard/wiki/%EA%B7%B8%EB%9E%98%ED%94%84-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EA%B0%9C%EB%B0%9C-%EA%B8%B0%EB%A1%9D) +2. [데이터 캐싱](https://github.com/boostcampwm2023/and09-PriceGuard/wiki/Cache-%EC%A0%81%EC%9A%A9%EA%B8%B0) +3. [DB 쿼리 κ°œμ„ ](https://velog.io/@kdogs/MongoDB%EC%97%90%EC%84%9C-%EC%BF%BC%EB%A6%AC-%EC%84%B1%EB%8A%A5-%EB%B6%84%EC%84%9D%ED%95%98%EA%B8%B0) + +[κ·Έλž˜ν”„ 라이브러리 κ΅¬ν˜„ 및 배포 μ €μž₯μ†Œ](https://github.com/Taewan-P/material-android-chart)λŠ” μ—¬κΈ°μ„œ ν™•μΈν•΄μ£Όμ„Έμš”. + +## ❓ 문제 ν•΄κ²° + +ν”„λ‘œμ νŠΈ 진행 κ³Όμ •μ—μ„œ λ§Œλ‚œ μ΄μŠˆλ“€κ³Ό ν•΄κ²° 방법에 λŒ€ν•΄ κΈ°μˆ ν•©λ‹ˆλ‹€. μ•„λž˜ λ§ν¬μ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. + +1. [μ•ˆλ“œλ‘œμ΄λ“œ ν…Œλ§ˆ 적용](https://github.com/boostcampwm2023/and09-PriceGuard/wiki/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%ED%85%8C%EB%A7%88-%EC%A0%81%EC%9A%A9) +2. [λ©”λͺ¨λ¦¬ λˆ„μˆ˜](https://github.com/boostcampwm2023/and09-PriceGuard/wiki/TroubleShooting-%E2%80%90-Memory-Leak) +3. [JWT 인증 처리](https://velog.io/@mks1103/JWT%EB%A1%9C-%EC%9D%B8%EC%A6%9D-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0) +4. [HTTPS](https://velog.io/@kdogs/HTTPS-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0feat.-Lets-Encrypt) +5. [Navigation λ°±μŠ€νƒ 였λ₯˜](https://github.com/boostcampwm2023/and09-PriceGuard/wiki/TroubleShooting-%E2%80%90-Navigation-%EB%B0%B1%EC%8A%A4%ED%83%9D-%EC%98%A4%EB%A5%98) +6. [ν…Œλ§ˆ λ³€κ²½](https://github.com/boostcampwm2023/and09-PriceGuard/wiki/ν…Œλ§ˆ-λ³€κ²½-(compose-vs-xml)) + +## πŸ“š 기술 μŠ€νƒ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
λΆ„λ₯˜κΈ°μˆ  μŠ€νƒ
+

μ•ˆλ“œλ‘œμ΄λ“œ

+
+ + + + + + + + + +
+

λ°±μ—”λ“œ

+
+ + + + + + + +
+

배포

+
+ + + + + + +
+

νŒ¨ν‚€μ§€ λ§€λ‹ˆμ €

+
+ + +
+

ν˜‘μ—…

+
+ + +
+ +## πŸ›οΈ μ‹œμŠ€ν…œ μ•„ν‚€ν…μ²˜ + +![μ‹œμŠ€ν…œ μ•„ν‚€ν…μ²˜](https://github.com/boostcampwm2023/and09-PriceGuard/assets/39708676/f7a943cd-5226-43dd-9130-9928f27067c4) + + ## πŸ‘¨β€πŸ‘¨β€πŸ‘¦ νŒ€μ› μ†Œκ°œ @@ -38,7 +147,16 @@ PriceGuardλŠ” κ΅­λ‚΄ μƒκ±°λž˜ μ‚¬μ΄νŠΈλ“€μ˜ μƒν’ˆ 가격을 μΆ”μ ν•©λ‹ˆλ‹€.

+## πŸ“ΊοΈŽ μž‘λ™ ν™”λ©΄ +| 둜그인/νšŒμ›κ°€μž… | μƒν’ˆ μΆ”μ²œ/μƒν’ˆ 상세 | μƒν’ˆ μΆ”κ°€ / λ§ˆμ΄νŽ˜μ΄μ§€ | μ•ŒλžŒ 확인 | +| ----------- | --------------- | ----------------- | ------- | +| | | | | + +## :memo: 기술 λ¬Έμ„œ +- [Feature List](https://docs.google.com/spreadsheets/d/1e1Z9YpHPZxcBZN2XBPeoaz88hDby6WG5jmMz8xjqMrU/edit#gid=1955813262) +- [칸반 λ³΄λ“œ](https://github.com/orgs/boostcampwm2023/projects/47/views/2) +- [κΈ°λŠ₯ λͺ…μ„Έ](https://github.com/boostcampwm2023/and09-PriceGuard/wiki/%EC%9A%94%EA%B5%AC%EC%82%AC%ED%95%AD-%EB%AA%85%EC%84%B8%EC%84%9C) +- [λ””μžμΈ](https://www.figma.com/file/lym7gZiLmcpXEKMw7UpSSp/Android-new?type=design&node-id=54696%3A327&mode=design&t=udiVXXmXkEnqYArE-1) +- [ERD](https://github.com/boostcampwm2023/and09-PriceGuard/wiki/ERD) -## :memo: νŒ€ κ·œμΉ™ -- [νŒ€ κ·œμΉ™](https://github.com/boostcampwm2023/and09-PriceGuard/wiki/%ED%8C%80-%EA%B7%9C%EC%B9%99) -- [Git κ·œμΉ™](https://github.com/boostcampwm2023/and09-PriceGuard/wiki/Git-%EC%82%AC%EC%9A%A9%EB%B2%95) +더 λ§Žμ€ μ •λ³΄λŠ” [μ €μž₯μ†Œ Wiki](https://github.com/boostcampwm2023/and09-PriceGuard/wiki)λ₯Ό μ°Έκ³ ν•΄μ£Όμ„Έμš”. diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 6f2d627..6ce775a 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -19,8 +19,8 @@ android { applicationId = "app.priceguard" minSdk = 29 targetSdk = 34 - versionCode = 6 - versionName = "0.3.2" + versionCode = 7 + versionName = "1.0.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/android/app/src/main/java/app/priceguard/service/PriceGuardFirebaseMessagingService.kt b/android/app/src/main/java/app/priceguard/service/PriceGuardFirebaseMessagingService.kt index e46e26a..79e9fd7 100644 --- a/android/app/src/main/java/app/priceguard/service/PriceGuardFirebaseMessagingService.kt +++ b/android/app/src/main/java/app/priceguard/service/PriceGuardFirebaseMessagingService.kt @@ -1,11 +1,111 @@ package app.priceguard.service +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.media.RingtoneManager +import android.net.Uri +import android.os.Build import android.util.Log +import androidx.core.app.NotificationCompat +import app.priceguard.R +import app.priceguard.ui.detail.DetailActivity +import com.bumptech.glide.Glide +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition import com.google.firebase.messaging.FirebaseMessagingService +import com.google.firebase.messaging.RemoteMessage class PriceGuardFirebaseMessagingService : FirebaseMessagingService() { // Init μ‹œμ—λ„ 호좜됨 override fun onNewToken(token: String) { Log.d("PriceGuardFirebaseMessagingService", "Refreshed token: $token") } + + override fun onMessageReceived(message: RemoteMessage) { + super.onMessageReceived(message) + message.notification?.let { + sendNotification( + it.title ?: return, + it.body ?: return, + it.imageUrl ?: return, + message.data["productCode"] ?: return + ) + } + } + + private fun sendNotification(title: String, body: String, imageUrl: Uri, data: String) { + val intent = Intent(this, DetailActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + intent.putExtra("productCode", data) + intent.putExtra("directed", true) + + val requestCode = getRequestCode() + val pendingIntent = PendingIntent.getActivity( + this, + requestCode, + intent, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + } else { + PendingIntent.FLAG_UPDATE_CURRENT + } + ) + + Glide.with(applicationContext) + .asBitmap() + .load(imageUrl) + .into(object : CustomTarget() { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + buildNotification(title, body, resource, pendingIntent, requestCode) + } + + override fun onLoadCleared(placeholder: Drawable?) {} + }) + } + + private fun buildNotification( + title: String, + body: String, + image: Bitmap, + pendingIntent: PendingIntent, + requestCode: Int + ) { + val channelId = getString(R.string.price_notification) + val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) + val notificationBuilder = NotificationCompat.Builder(this, channelId) + .setSmallIcon(R.drawable.ic_priceguard_notification) + .setContentTitle(title) + .setContentText(body) + .setAutoCancel(true) + .setSound(defaultSoundUri) + .setLargeIcon(image) + .setStyle(NotificationCompat.BigPictureStyle().bigPicture(image)) + .setContentIntent(pendingIntent) + + val notificationManager = + getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + val channel = NotificationChannel( + channelId, + getString(R.string.priceguard_push_alarm_title), + NotificationManager.IMPORTANCE_DEFAULT + ) + notificationManager.createNotificationChannel(channel) + + notificationManager.notify(requestCode, notificationBuilder.build()) + } + + companion object { + private var requestCode = 0 + + fun getRequestCode(): Int { + requestCode = (requestCode + 1) % 100 + return requestCode + } + } } diff --git a/android/app/src/main/java/app/priceguard/ui/additem/setprice/SetTargetPriceFragment.kt b/android/app/src/main/java/app/priceguard/ui/additem/setprice/SetTargetPriceFragment.kt index b3889e3..6e76c08 100644 --- a/android/app/src/main/java/app/priceguard/ui/additem/setprice/SetTargetPriceFragment.kt +++ b/android/app/src/main/java/app/priceguard/ui/additem/setprice/SetTargetPriceFragment.kt @@ -123,14 +123,18 @@ class SetTargetPriceFragment : Fragment() { private fun updateTargetPriceUI(it: Editable?) { if (binding.etTargetPrice.isFocused) { - val targetPrice = if (it.toString().matches("^\\d+\$".toRegex())) { - it.toString().toFloat() + val targetPrice = if (it.toString().matches("^\\d{1,9}$".toRegex())) { + it.toString().toInt() + } else if (it.toString().isEmpty()) { + binding.etTargetPrice.setText(getString(R.string.min_price)) + 0 } else { - 0F + binding.etTargetPrice.setText(getString(R.string.max_price)) + 999999999 } - setTargetPriceViewModel.updateTargetPrice(targetPrice.toInt()) - binding.updateSlideValueWithPrice(targetPrice) + setTargetPriceViewModel.updateTargetPrice(targetPrice) + binding.updateSlideValueWithPrice(targetPrice.toFloat()) } } @@ -212,9 +216,8 @@ class SetTargetPriceFragment : Fragment() { private fun FragmentSetTargetPriceBinding.updateSlideValueWithPrice(targetPrice: Float) { val percent = - ((targetPrice / setTargetPriceViewModel.state.value.productPrice) * MAX_PERCENT).toInt().roundAtFirstDigit() - - val pricePercent = percent.coerceIn(MIN_PERCENT, MAX_PERCENT) + ((targetPrice / setTargetPriceViewModel.state.value.productPrice) * MAX_PERCENT).toInt() + val pricePercent = percent.coerceIn(MIN_PERCENT, MAX_PERCENT).roundAtFirstDigit() if (targetPrice > setTargetPriceViewModel.state.value.productPrice) { tvTargetPricePercent.text = getString(R.string.over_current_price) } else { diff --git a/android/app/src/main/java/app/priceguard/ui/util/Dialog.kt b/android/app/src/main/java/app/priceguard/ui/util/Dialog.kt index f635091..b1f3e18 100644 --- a/android/app/src/main/java/app/priceguard/ui/util/Dialog.kt +++ b/android/app/src/main/java/app/priceguard/ui/util/Dialog.kt @@ -3,7 +3,6 @@ package app.priceguard.ui.util import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import app.priceguard.ui.ErrorDialogFragment import app.priceguard.ui.data.DialogConfirmAction fun AppCompatActivity.showConfirmDialog( diff --git a/android/app/src/main/java/app/priceguard/ui/ErrorDialogFragment.kt b/android/app/src/main/java/app/priceguard/ui/util/ErrorDialogFragment.kt similarity index 98% rename from android/app/src/main/java/app/priceguard/ui/ErrorDialogFragment.kt rename to android/app/src/main/java/app/priceguard/ui/util/ErrorDialogFragment.kt index 0ab68b0..daccc18 100644 --- a/android/app/src/main/java/app/priceguard/ui/ErrorDialogFragment.kt +++ b/android/app/src/main/java/app/priceguard/ui/util/ErrorDialogFragment.kt @@ -1,4 +1,4 @@ -package app.priceguard.ui +package app.priceguard.ui.util import android.app.Dialog import android.content.Intent diff --git a/android/app/src/main/res/layout/activity_detail.xml b/android/app/src/main/res/layout/activity_detail.xml index 327baf1..f38f68a 100644 --- a/android/app/src/main/res/layout/activity_detail.xml +++ b/android/app/src/main/res/layout/activity_detail.xml @@ -109,7 +109,7 @@ android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:ellipsize="end" - android:singleLine="true" + android:maxLines="3" android:text="@{viewModel.state.productName}" android:textAppearance="?attr/textAppearanceTitleLarge" app:layout_constraintEnd_toStartOf="@id/btn_detail_share" diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 25d621c..148d8e3 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -130,4 +130,7 @@ ν˜„μž¬ κ°€κ²©μ˜ 80% μ—­λŒ€ μ΅œμ €κ°€ %s λͺ©ν‘œ 가격 %s 으둜 섀정됨 + 0 + 999999999 + Product Price Notification \ No newline at end of file diff --git a/android/release_notes.txt b/android/release_notes.txt index f78a227..2e6a1f3 100644 --- a/android/release_notes.txt +++ b/android/release_notes.txt @@ -1,9 +1,12 @@ -December 12, 2023 -v0.3.2 +December 14, 2023 +v1.0.0 + +πŸš€ Feature Updates + +- 앱이 Foreground에 μžˆμ–΄λ„ μ•Œλ¦Όμ΄ μ˜€κ²Œλ” 지원 + πŸ”§ Bug Fixes -- κ³΅μœ ν•˜κΈ°λ‘œ μƒν’ˆ 등둝 μ™„λ£Œ ν›„ 앱이 κΊΌμ§€λŠ” ν˜„μƒ μˆ˜μ • -- μƒν’ˆ 쀑볡 등둝 μ‹œ ν™ˆ ν™”λ©΄μœΌλ‘œ λŒμ•„κ°€λ„λ‘ μˆ˜μ • -- μƒν’ˆ μˆ˜μ • μ‹œ μƒμ„Έν™”λ©΄μœΌλ‘œ λŒμ•„κ°€λ„λ‘ μˆ˜μ • -- μ‹œμž‘ν™”λ©΄, 둜그인 ν™”λ©΄μ—μ„œ 글씨 크기 초과 μ‹œ ν…μŠ€νŠΈ 크기 μžλ™ μ‘°μ • +- λͺ©ν‘œ 가격 μ„€μ • λ²”μœ„κ°€ 0~999999999 μ‚¬μ΄μ—μ„œ μ„€μ •λ˜λ„λ‘ μˆ˜μ • +- μƒν’ˆ 상세 νŽ˜μ΄μ§€ 제λͺ© μ΅œλŒ€ 쀄 수λ₯Ό 3μ€„λ‘œ λ³€κ²½ diff --git a/backend/src/cache/cache.service.ts b/backend/src/cache/cache.service.ts index 60c6119..6c53d68 100644 --- a/backend/src/cache/cache.service.ts +++ b/backend/src/cache/cache.service.ts @@ -98,6 +98,10 @@ export class CacheService { return this.trackingProductCache.get(key); } + getAllTrackingProduct() { + return this.trackingProductCache.getAll(); + } + addValueTrackingProduct(key: string, value: TrackingProduct) { this.trackingProductCache.addValue(key, value); } diff --git a/backend/src/cache/tracking.cache.ts b/backend/src/cache/tracking.cache.ts index c6b2b30..54d85a2 100644 --- a/backend/src/cache/tracking.cache.ts +++ b/backend/src/cache/tracking.cache.ts @@ -20,6 +20,7 @@ export class TrackingProductCache { this.delete(key); const node = new CacheNode(key, value); this.add(node); + this.hashMap.set(key, node); if (this.count > this.maxSize) { const oldestNode = this.head.next; this.remove(oldestNode); @@ -32,7 +33,6 @@ export class TrackingProductCache { node.next = this.tail; node.prev = prev; this.tail.prev = node; - this.hashMap.set(node.key, node); this.count++; } @@ -40,6 +40,7 @@ export class TrackingProductCache { const node = this.hashMap.get(key); if (node) { this.remove(node); + this.hashMap.delete(key); } } @@ -47,7 +48,6 @@ export class TrackingProductCache { const { prev, next } = node; prev.next = next; next.prev = prev; - this.hashMap.delete(node.key); this.count--; } @@ -67,7 +67,12 @@ export class TrackingProductCache { get(key: string): TrackingProduct[] | null { const node = this.hashMap.get(key); - return node ? node.value : null; + if (node) { + this.remove(node); + this.add(node); + return node.value; + } + return null; } getAll() { @@ -84,6 +89,8 @@ export class TrackingProductCache { const node = this.hashMap.get(key); if (node) { node.value.push(value); + this.remove(node); + this.add(node); } } @@ -93,6 +100,8 @@ export class TrackingProductCache { node.value = node.value.filter((product) => { return product.productId !== value.productId; }); + this.remove(node); + this.add(node); } } @@ -105,6 +114,8 @@ export class TrackingProductCache { return false; } }); + this.remove(node); + this.add(node); } } diff --git a/backend/src/constants.ts b/backend/src/constants.ts index e753d08..d315208 100644 --- a/backend/src/constants.ts +++ b/backend/src/constants.ts @@ -39,3 +39,4 @@ export const TWENTY_MIN_TO_SEC = 20 * 60; export const TWO_WEEKS_TO_SEC = 2 * 7 * 24 * 60 * 60; export const TWO_MONTHS_TO_SEC = 61 * 24 * 60 * 60; export const MAX_TRACKING_PRODUCT_CACHE = parseInt(process.env.MAX_TRACKING_PRODUCT_CACHE || '30'); +export const MAX_TARGET_PRICE = 999999999; diff --git a/backend/src/dto/product.add.dto.ts b/backend/src/dto/product.add.dto.ts index 1fe4e13..220b806 100644 --- a/backend/src/dto/product.add.dto.ts +++ b/backend/src/dto/product.add.dto.ts @@ -1,5 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; +import { IsNotEmpty, IsNumber, IsString, Max } from 'class-validator'; +import { MAX_TARGET_PRICE } from 'src/constants'; export class ProductAddDto { @ApiProperty({ @@ -16,6 +17,7 @@ export class ProductAddDto { required: true, }) @IsNumber() + @Max(MAX_TARGET_PRICE) @IsNotEmpty() targetPrice: number; } diff --git a/backend/src/product/product.service.ts b/backend/src/product/product.service.ts index 8e3dce2..5f87b97 100644 --- a/backend/src/product/product.service.ts +++ b/backend/src/product/product.service.ts @@ -12,7 +12,7 @@ import { InjectModel } from '@nestjs/mongoose'; import { ProductPrice } from 'src/schema/product.schema'; import { Model } from 'mongoose'; import { PriceDataDto } from 'src/dto/price.data.dto'; -import { MAX_TRACKING_RANK, NINETY_DAYS, THIRTY_DAYS, TWENTY_MIN_TO_SEC } from 'src/constants'; +import { MAX_TRACKING_RANK, NINETY_DAYS, THIRTY_DAYS } from 'src/constants'; import { ProductRankCacheDto } from 'src/dto/product.rank.cache.dto'; import Redis from 'ioredis'; import { InjectRedis } from '@songkeys/nestjs-redis'; @@ -159,6 +159,7 @@ export class ProductService { const product = await this.findTrackingProductByCode(userId, productAddDto.productCode); product.targetPrice = productAddDto.targetPrice; product.isFirst = true; + this.cacheService.updateValueTrackingProdcut(userId, product); await this.trackingProductRepository.save(product); } @@ -264,8 +265,6 @@ export class ProductService { isSoldOut: productInfo.isSoldOut, lowestPrice: productInfo.productPrice, }), - 'EX', - TWENTY_MIN_TO_SEC, ); this.productPriceModel.create(updatedDataInfo); return product;