cfd550bed8
Deploy via SSH / remote-deploy (push) Failing after 6s
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>
79 lines
2.1 KiB
TypeScript
79 lines
2.1 KiB
TypeScript
import { create } from 'zustand';
|
|
import { authApi, type User } from '@/api/endpoints';
|
|
import { clearToken, loadToken, saveToken } from '@/api/client';
|
|
|
|
type State = {
|
|
user: User | null;
|
|
loading: boolean;
|
|
error: string | null;
|
|
hydrate: () => Promise<void>;
|
|
login: (email: string, password: string) => Promise<void>;
|
|
register: (body: Parameters<typeof authApi.register>[0]) => Promise<void>;
|
|
kakaoLogin: (accessToken: string) => Promise<void>;
|
|
logout: () => Promise<void>;
|
|
};
|
|
|
|
export const useAuthStore = create<State>((set) => ({
|
|
user: null,
|
|
loading: true,
|
|
error: null,
|
|
|
|
hydrate: async () => {
|
|
const tok = await loadToken();
|
|
if (!tok) {
|
|
set({ loading: false });
|
|
return;
|
|
}
|
|
try {
|
|
const me = await authApi.me();
|
|
set({ user: me, loading: false });
|
|
// push registration (non-blocking)
|
|
import('@/services/push').then((m) => m.registerForPushAsync()).catch(() => {});
|
|
} catch {
|
|
await clearToken();
|
|
set({ user: null, loading: false });
|
|
}
|
|
},
|
|
|
|
login: async (email, password) => {
|
|
set({ loading: true, error: null });
|
|
try {
|
|
const res = await authApi.login(email, password);
|
|
await saveToken(res.token);
|
|
set({ user: res.user, loading: false });
|
|
} catch (e: any) {
|
|
set({ loading: false, error: e?.message ?? '로그인 실패' });
|
|
throw e;
|
|
}
|
|
},
|
|
|
|
register: async (body) => {
|
|
set({ loading: true, error: null });
|
|
try {
|
|
const res = await authApi.register(body);
|
|
await saveToken(res.token);
|
|
set({ user: res.user, loading: false });
|
|
} catch (e: any) {
|
|
set({ loading: false, error: e?.message ?? '회원가입 실패' });
|
|
throw e;
|
|
}
|
|
},
|
|
|
|
kakaoLogin: async (accessToken) => {
|
|
set({ loading: true, error: null });
|
|
try {
|
|
const res = await authApi.kakao(accessToken);
|
|
await saveToken(res.token);
|
|
set({ user: res.user, loading: false });
|
|
} catch (e: any) {
|
|
set({ loading: false, error: e?.message ?? '카카오 로그인 실패' });
|
|
throw e;
|
|
}
|
|
},
|
|
|
|
logout: async () => {
|
|
await clearToken();
|
|
set({ user: null });
|
|
},
|
|
}));
|