Files
chpark cfd550bed8
Deploy via SSH / remote-deploy (push) Failing after 6s
feat: AI판정/OCR/알림톡/소셜로그인/푸시/CODEF 전체 구현 + CI SSH 전환
Backend (server/src):
- services/anthropic.ts — Claude API 래퍼 (키 없으면 룰베이스 fallback)
- services/ocr.ts — Naver Clova + Google Vision 듀얼 연동 + 영수증 필드 파서
- services/solapi.ts — 카카오 알림톡 HMAC 서명 + 드라이런
- services/expoPush.ts — Expo Push API 전송
- services/codef.ts — 보험 통합조회 mock + 실연동 포인트
- routes/ai.ts, ocr.ts, devices.ts, social.ts (naver/apple), alimtalk.ts, codef.ts
- Prisma: PushDevice 모델 + binaryTargets linux-musl-openssl-3.0.x
- Dockerfile: apk add openssl (Prisma schema engine 정상화)
- api-secrets에 9개 외부 API 키 슬롯 추가 (optional)

Frontend:
- api/endpoints.ts: aiApi, ocrApi, deviceApi, socialApi, codefApi
- services/kakao.ts — Kakao JS SDK 동적 로드 + Auth.login
- services/push.ts — expo-notifications 권한/토큰 등록 + 서버 전송
- LoginScreen — 카카오/네이버/애플 버튼 (웹은 토큰 입력 fallback)
- AIJudgeScreen — 실제 /ai/claim-judge 호출, source(llm/rules) 표시
- ClaimScreen — 영수증 촬영 시 자동 OCR → 병원/날짜/제목 자동 기입
- useAuthStore hydrate 시 푸시 토큰 등록

Infra:
- eas.json (development/preview/production 빌드 프로필)
- API_KEYS.md — 9개 외부 서비스 발급/등록 가이드
- scripts/deploy-remote.sh 개선 (sudo 정확히, traefik cp 버그 수정, API fail 시 로그 출력)
- deploy/k8s/api.yaml — 외부 API 키 환경변수 매핑 (optional=true)

CI/CD:
- .gitea/workflows/deploy.yml → SSH 기반으로 전환
  (appleboy/ssh-action으로 서버 접속 → deploy-remote.sh 실행)
- 필요 Secrets: SSH_HOST, SSH_USER, SSH_PASSWORD

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 00:56:06 +09:00

275 lines
5.6 KiB
Plaintext

generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "linux-musl", "linux-musl-openssl-3.0.x"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum AuthProvider {
EMAIL
KAKAO
APPLE
NAVER
GOOGLE
}
model User {
id String @id @default(cuid())
email String? @unique
passwordHash String?
name String
phone String?
provider AuthProvider @default(EMAIL)
kakaoId String? @unique
appleSub String? @unique
naverId String? @unique
googleId String? @unique
profileImage String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
profile Profile?
family FamilyMember[]
policies Policy[]
claims Claim[]
notifications Notification[]
diagnoses Diagnosis[]
healthChecks HealthCheck[]
consults Consult[]
devices PushDevice[]
}
model Profile {
id String @id @default(cuid())
userId String @unique
age Int
gender Gender
job String?
monthlyPremium Int @default(0)
score Int @default(0)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
enum Gender {
MALE
FEMALE
}
enum Relation {
SELF
SPOUSE
CHILD
PARENT
SIBLING
}
model FamilyMember {
id String @id @default(cuid())
userId String
relation Relation
name String
age Int
gender Gender
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
policies Policy[]
@@index([userId])
}
enum PolicyType {
SILSON
CANCER
LIFE
ACCIDENT
CHILD
NURSING
FEMALE
DENTAL
DRIVER
CAR
}
model Policy {
id String @id @default(cuid())
userId String
familyMemberId String?
name String
insurer String
type PolicyType
monthlyPremium Int
coverage BigInt
joinDate DateTime
maturityDate DateTime?
renewalDate DateTime?
silsonGen Int?
isGroup Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
familyMember FamilyMember? @relation(fields: [familyMemberId], references: [id], onDelete: SetNull)
@@index([userId])
@@index([familyMemberId])
}
enum ClaimStatus {
SUBMITTED
REVIEWING
ADDITIONAL_DOCS
APPROVED
PAID
REJECTED
}
model Claim {
id String @id @default(cuid())
userId String
title String
hospital String?
visitDate DateTime?
status ClaimStatus @default(SUBMITTED)
amount Int?
aiEstimated String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
attachments ClaimAttachment[]
events ClaimEvent[]
@@index([userId, status])
}
enum AttachmentType {
RECEIPT
DIAGNOSIS
DETAIL
OTHER
}
model ClaimAttachment {
id String @id @default(cuid())
claimId String
type AttachmentType
objectKey String
mimeType String?
size Int?
createdAt DateTime @default(now())
claim Claim @relation(fields: [claimId], references: [id], onDelete: Cascade)
@@index([claimId])
}
model ClaimEvent {
id String @id @default(cuid())
claimId String
status ClaimStatus
note String?
createdAt DateTime @default(now())
claim Claim @relation(fields: [claimId], references: [id], onDelete: Cascade)
@@index([claimId])
}
enum NotificationTone {
INFO
WARN
DANGER
}
model Notification {
id String @id @default(cuid())
userId String
title String
body String
tone NotificationTone @default(INFO)
scheduled DateTime
readAt DateTime?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, scheduled])
}
model Diagnosis {
id String @id @default(cuid())
userId String
answers Json
score Int
breakdown Json
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
}
model HealthCheck {
id String @id @default(cuid())
userId String
date DateTime
metrics Json
summary String?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, date])
}
enum ConsultMethod {
KAKAO
PHONE
VISIT
}
enum ConsultStatus {
REQUESTED
SCHEDULED
DONE
CANCELED
}
model Consult {
id String @id @default(cuid())
userId String
method ConsultMethod
phone String?
preferredAt DateTime?
memo String?
status ConsultStatus @default(REQUESTED)
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, status])
}
enum DevicePlatform {
ios
android
web
}
model PushDevice {
id String @id @default(cuid())
userId String
expoPushToken String @unique
platform DevicePlatform
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
}