Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions .claude/hooks/git-commit-guard.sh
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
#!/usr/bin/env bash
# PreToolUse Bash hook: block all `git commit` invocations.
# PreToolUse Bash hook: `git commit` 정책.
#
# 사용자가 Android Studio 커밋 탭에서 직접 검토·커밋하는 워크플로우. Claude의 자율 커밋은 절대 금지.
# 2026-09-05 이전: 사용자가 Android Studio 커밋 탭에서 직접 검토·커밋하는 워크플로우라 Claude 의 `git commit` 을 전부 막았다.
# 2026-09-05 지시("이 프로젝트는 내가 검토할 시간이 없으니 네가 알아서 해라"): 이 저장소에서는 Claude 가 검증을 마친 뒤
# 커밋·push·PR 생성·머지까지 수행한다. 이 훅은 검증을 건너뛰는 `--no-verify` 커밋만 막는다.
# 되돌리려면 이 파일의 이전 판(전부 deny)을 git 이력에서 복원하고 CLAUDE.md 의 규약 문구를 함께 되돌린다.
set -uo pipefail

input="$(cat)"
cmd="$(echo "$input" | jq -r '.tool_input.command // empty')"
[ -z "$cmd" ] && exit 0

# `git commit` 가 단어 경계 단위로 나타나면 차단 (`git commit-tree` 같은 다른 서브커맨드는 영향 없음)
if [[ "$cmd" =~ (^|[[:space:]]|\;|\&|\|)git[[:space:]]+commit([[:space:]]|$) ]]; then
# 예외: `git commit --amend -m "..."` 또는 `--amend --message=...` 는 *메시지만 수정* 용도로 허용.
# 주의: staged changes 가 있으면 amend 시 함께 커밋됨. 호출 전에 staging 이 비어있는지 확인할 책임은 호출자.
if [[ "$cmd" =~ --amend ]] && [[ "$cmd" =~ (-m[[:space:]]|--message) ]]; then
exit 0
fi
jq -nc --arg reason "자율 커밋 금지. 사용자가 Android Studio 커밋 탭에서 직접 검토·커밋합니다. 정말 커밋이 필요하면 사용자에게 직접 실행을 요청하세요. (예외: \`git commit --amend -m\` 으로 마지막 커밋 메시지만 수정하는 케이스는 허용.)" \
if [[ "$cmd" =~ (^|[[:space:]]|\;|\&|\|)git[[:space:]]+commit([[:space:]]|$) ]] && [[ "$cmd" =~ --no-verify ]]; then
jq -nc --arg reason "git commit --no-verify 금지. 훅 검증을 건너뛰지 말고 실패 원인을 고친 뒤 커밋하세요." \
'{hookSpecificOutput: {hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: $reason}}'
fi

Expand Down
1 change: 1 addition & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .mailmap
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# .mailmap — git author 정규화
#
# 아래 표시 이름들은 모두 동일 인물(정일혁, dnfjddk2@gmail.com)의 커밋입니다.
# 초기에 git user.name 이 시기마다 달라 author 가 4개로 분산됐습니다:
# explodeTheComputer · Dive on fish · Zach · 일혁 → 정일혁
#
# 이메일이 모두 같으므로 아래 한 줄이 4개를 전부 "정일혁" 으로 정규화합니다.
# (커밋 히스토리·SHA 는 바뀌지 않으며, git shortlog / log --use-mailmap / blame 의
# 표시에만 적용됩니다. GitHub 은 .mailmap 을 반영하지 않습니다.)
정일혁 <dnfjddk2@gmail.com>
5 changes: 4 additions & 1 deletion app/src/main/java/com/example/slowclock/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@ import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.messaging.FirebaseMessaging
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
private lateinit var authManager: AuthManager
private val dummyDataManager = DummyDataManager()

@Inject
lateinit var dummyDataManager: DummyDataManager

private fun handleNewInstallation() {
val prefs = getSharedPreferences("app_state", MODE_PRIVATE)
Expand Down
5 changes: 5 additions & 0 deletions core/alarm/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.hilt)
alias(libs.plugins.ksp)
}

android {
Expand All @@ -21,8 +23,11 @@ android {

dependencies {
api(project(":core:model"))
implementation(project(":core:data")) // Notifier 인터페이스 구현 (역결합 제거)
implementation(libs.androidx.core.ktx)
implementation(libs.firebase.messaging)
implementation(libs.firebase.auth)
implementation(libs.volley)
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@ import android.content.Context
import android.util.Log
import com.android.volley.toolbox.JsonObjectRequest
import com.android.volley.toolbox.Volley
import com.example.slowclock.data.notification.Notifier
import org.json.JSONObject
import java.lang.reflect.Method

object GuardianNotifier {
object GuardianNotifier : Notifier {
// Replace with your deployed Cloud Function URL
private const val CLOUD_FUNCTION_URL = "https://us-central1-slow-clock-scheduler.cloudfunctions.net/sendFcmNotification"

fun sendReminderToUser(
override fun sendReminderToUser(
context: Context,
fcmToken: String,
title: String,
message: String,
shareCode: String? = null,
shareCode: String?,
) {
val json =
JSONObject().apply {
Expand Down Expand Up @@ -45,12 +46,12 @@ object GuardianNotifier {
Volley.newRequestQueue(context).add(request)
}

fun sendReminderToUsers(
override fun sendReminderToUsers(
context: Context,
fcmTokens: List<String>,
title: String,
message: String,
shareCode: String? = null,
shareCode: String?,
) {
for (token in fcmTokens) {
sendReminderToUser(context, token, title, message, shareCode)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.example.slowclock.notification

import com.example.slowclock.data.notification.Notifier
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent

/**
* [Notifier] 추상화에 :core:alarm 의 [GuardianNotifier] 구현을 바인딩한다.
*
* :core:data 의 Repository 들은 이 모듈을 통해 [Notifier] 를 주입받는다.
*/
@Module
@InstallIn(SingletonComponent::class)
object NotifierModule {
@Provides
fun provideNotifier(): Notifier = GuardianNotifier
}
1 change: 0 additions & 1 deletion core/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ android {
dependencies {
api(project(":core:model"))
implementation(project(":core:common"))
implementation(project(":core:alarm")) // GuardianNotifier (FCM 전송) 호출

implementation(libs.firebase.auth)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,66 +6,69 @@ import com.example.slowclock.data.model.Schedule
import com.example.slowclock.data.remote.repository.ScheduleRepository
import com.google.firebase.Timestamp
import java.util.Calendar
import javax.inject.Inject

class DummyDataManager {
private val scheduleRepository = ScheduleRepository()
class DummyDataManager
@Inject
constructor(
private val scheduleRepository: ScheduleRepository,
) {
suspend fun addDummyDataIfNeeded() {
try {
// 새로운 ScheduleResult 형태로 수정
when (val result = scheduleRepository.getSchedulesForDate(Calendar.getInstance())) {
is ScheduleRepository.ScheduleResult.Success -> {
val existing = result.data

suspend fun addDummyDataIfNeeded() {
try {
// 새로운 ScheduleResult 형태로 수정
when (val result = scheduleRepository.getSchedulesForDate(Calendar.getInstance())) {
is ScheduleRepository.ScheduleResult.Success -> {
val existing = result.data
if (existing.isEmpty()) {
Log.d("DUMMY", "일정 없음, 더미 데이터 추가")

if (existing.isEmpty()) {
Log.d("DUMMY", "일정 없음, 더미 데이터 추가")
val schedules =
listOf(
Schedule(title = "아침 운동", startTime = getTodayTime(9, 0)),
Schedule(
title = "점심 약속",
startTime = getTodayTime(12, 30),
completed = true,
),
Schedule(title = "저녁 산책", startTime = getTodayTime(18, 0)),
Schedule(title = "지금 할 일", startTime = Timestamp.now()),
)

val schedules =
listOf(
Schedule(title = "아침 운동", startTime = getTodayTime(9, 0)),
Schedule(
title = "점심 약속",
startTime = getTodayTime(12, 30),
completed = true,
),
Schedule(title = "저녁 산책", startTime = getTodayTime(18, 0)),
Schedule(title = "지금 할 일", startTime = Timestamp.now()),
)
schedules.forEach { schedule ->
when (val addResult = scheduleRepository.addSchedule(schedule)) {
is ScheduleRepository.ScheduleResult.Success -> {
Log.d("DUMMY", "더미 일정 추가 성공: ${schedule.title}")
}

schedules.forEach { schedule ->
when (val addResult = scheduleRepository.addSchedule(schedule)) {
is ScheduleRepository.ScheduleResult.Success -> {
Log.d("DUMMY", "더미 일정 추가 성공: ${schedule.title}")
}

is ScheduleRepository.ScheduleResult.Error -> {
Log.e("DUMMY", "더미 일정 추가 실패: ${addResult.error.message}")
is ScheduleRepository.ScheduleResult.Error -> {
Log.e("DUMMY", "더미 일정 추가 실패: ${addResult.error.message}")
}
}
}
Log.d("DUMMY", "더미 데이터 추가 완료")
} else {
Log.d("DUMMY", "이미 일정 있음: ${existing.size}개")
}
Log.d("DUMMY", "더미 데이터 추가 완료")
} else {
Log.d("DUMMY", "이미 일정 있음: ${existing.size}개")
}
}

is ScheduleRepository.ScheduleResult.Error -> {
Log.e("DUMMY", "일정 조회 실패: ${result.error.message}")
is ScheduleRepository.ScheduleResult.Error -> {
Log.e("DUMMY", "일정 조회 실패: ${result.error.message}")
}
}
} catch (e: Exception) {
Log.e("DUMMY", "더미 데이터 추가 실패", e)
}
} catch (e: Exception) {
Log.e("DUMMY", "더미 데이터 추가 실패", e)
}
}

private fun getTodayTime(
hour: Int,
minute: Int,
): Timestamp {
val calendar = Calendar.getInstance()
calendar.set(Calendar.HOUR_OF_DAY, hour)
calendar.set(Calendar.MINUTE, minute)
calendar.set(Calendar.SECOND, 0)
return Timestamp(calendar.time)
private fun getTodayTime(
hour: Int,
minute: Int,
): Timestamp {
val calendar = Calendar.getInstance()
calendar.set(Calendar.HOUR_OF_DAY, hour)
calendar.set(Calendar.MINUTE, minute)
calendar.set(Calendar.SECOND, 0)
return Timestamp(calendar.time)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.example.slowclock.data.notification

import android.content.Context

/**
* 데이터 레이어가 알림 전송을 요청하기 위한 추상화.
*
* 구현체(GuardianNotifier)는 :core:alarm 에 있고,
* Hilt 로 주입된다. 이로써 :core:data 가 :core:alarm 에 직접 의존하지 않는다(레이어 역전 제거).
*/
interface Notifier {
fun sendReminderToUser(
context: Context,
fcmToken: String,
title: String,
message: String,
shareCode: String? = null,
)

fun sendReminderToUsers(
context: Context,
fcmTokens: List<String>,
title: String,
message: String,
shareCode: String? = null,
)
}
Loading
Loading