작성일: 2026년 3월 23일 진행 단계: Phase 6 (백엔드 개발) 진행 중 이전 일지: Day 3 참고 (Phase 1~5 완료)
| API 도메인 | 엔드포인트 수 | 상태 |
|---|---|---|
| Auth | 5개 | ✅ 완료 |
| Medications | 5개 | ✅ 완료 |
| Schedules | 1개 | 🔜 다음 |
| Dose | 4개 | ⬜ 대기 |
| Notifications | 3개 | ⬜ 대기 |
| 합계 | 18개 | 10개 완료 / 8개 대기 |
초기 설계에서 medications.user_id 가 public.users.id 를 참조했으나, Supabase Auth는 auth.users 를 별도로 관리하여 FK 오류 발생.
Error: insert or update on table "medications" violates foreign key constraint
Key (user_id)=(uuid) is not present in table "users"
방법 A vs 방법 B 비교
| 방법 A (auth.users 직접 참조) | 방법 B (public.users 별도 유지) | |
|---|---|---|
| 개발 속도 | 빠름 | 느림 |
| 확장성 | 낮음 | 높음 |
| 글로벌 런칭 대비 | 부족 | 적합 |
| 보호자 연동 (v2) | 어려움 | 쉬움 |
→ 글로벌 런칭과 보호자 연동을 고려해 방법 B 채택
-- users (auth.users 참조)
CREATE TABLE users (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
email TEXT UNIQUE,
name TEXT NOT NULL DEFAULT '',
timezone TEXT NOT NULL DEFAULT 'Asia/Seoul',
language TEXT NOT NULL DEFAULT 'ko',
provider TEXT NOT NULL DEFAULT 'email',
created_at TIMESTAMPTZ DEFAULT now(),
deleted_at TIMESTAMPTZ
);
-- medications
CREATE TABLE medications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name TEXT NOT NULL,
dosage TEXT,
color_tag TEXT DEFAULT '#1D9E75',
memo TEXT,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_medications_user ON medications(user_id)
WHERE deleted_at IS NULL;
-- schedules
CREATE TABLE schedules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
medication_id UUID NOT NULL REFERENCES medications(id) ON DELETE CASCADE,
scheduled_time TIME NOT NULL,
cycle_type TEXT NOT NULL,
cycle_value JSONB,
is_active BOOLEAN DEFAULT true
);
CREATE INDEX idx_schedules_medication ON schedules(medication_id);
-- dose_logs
CREATE TABLE dose_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
schedule_id UUID NOT NULL REFERENCES schedules(id),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
log_date DATE NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
taken_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now(),
UNIQUE (schedule_id, log_date)
);
CREATE INDEX idx_dose_logs_user_date ON dose_logs(user_id, log_date DESC);
CREATE INDEX idx_dose_logs_schedule ON dose_logs(schedule_id);
-- notification_logs
CREATE TABLE notification_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
schedule_id UUID NOT NULL REFERENCES schedules(id),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
sent_at TIMESTAMPTZ DEFAULT now(),
status TEXT NOT NULL,
snooze_count INT DEFAULT 0
);
CREATE INDEX idx_notif_logs_user ON notification_logs(user_id, sent_at DESC);
-- device_tokens
CREATE TABLE device_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token TEXT NOT NULL UNIQUE,
platform TEXT NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX idx_device_tokens_user ON device_tokens(user_id);
app/schemas/auth.py ← 요청/응답 Pydantic 모델
app/services/auth_service.py ← 비즈니스 로직
app/routers/auth.py ← API 엔드포인트