Skip to content
Open
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
27 changes: 14 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,23 @@ EXPO_PUBLIC_MOCK_API_FAIL_ENDPOINTS=playlist.detail npm run web
실제 서버와 연동할 때는 Expo 환경변수로 API base URL을 지정합니다. 이 값이 없으면 mock으로 돌아가지 않고 API URL 누락 오류를 표시합니다.

```bash
EXPO_PUBLIC_SOUNDLOG_API_SOURCE=server EXPO_PUBLIC_SOUNDLOG_API_BASE_URL=/api/soundlog npm run web
EXPO_PUBLIC_SOUNDLOG_API_SOURCE=server EXPO_PUBLIC_SOUNDLOG_API_BASE_URL=https://api.soundlog.shop npm run ios
```

Vercel web 배포는 브라우저 API 호출을 같은 origin에서 처리하도록
`https://soundlog.shop/api/soundlog` rewrite proxy를 사용합니다. 브라우저와 네이티브 앱은 별도 `api` 서브도메인을 쓰지 않습니다. Vercel 내부 rewrite 대상은
`SOUNDLOG_API_ORIGIN` 환경변수로 관리하며, 이 값은 최신 SoundLogServer origin이어야 합니다. `vercel.mjs`의 build command가
`EXPO_PUBLIC_SOUNDLOG_API_SOURCE=server`와
`EXPO_PUBLIC_SOUNDLOG_API_BASE_URL=/api/soundlog`를 주입합니다.
Soundlog는 웹 서비스를 배포하지 않습니다. 운영 배포 대상은 Expo/EAS로 빌드한 iOS·Android 네이티브 앱이며, 앱은 Vercel 프록시 없이 GCP의 `https://api.soundlog.shop`을 직접 호출합니다. 저장소의 web·Vercel 관련 설정은 로컬 호환성 확인과 과거 실험용 자료일 뿐 운영 배포 경로에 포함하지 않습니다.

네이티브 실배포 빌드는 `https://soundlog.shop/api/soundlog` HTTPS 프록시를 사용합니다. 로그인은 Soundlog 자체 이메일/비밀번호 계정으로 처리합니다.
네이티브 실배포 빌드는 `api.soundlog.shop` 서브도메인을 직접 호출합니다(GCP VM 위 Caddy가 HTTPS를 종료). 로그인은 Soundlog 자체 이메일/비밀번호 계정으로 처리합니다.

## 테스트 설치 빌드

`development`, `preview` EAS profile은 HTTPS Vercel API proxy를 바라보도록 설정되어 있습니다.
`development`, `preview` EAS profile은 `api.soundlog.shop`을 직접 바라보도록 설정되어 있습니다.

- Web: `https://soundlog.shop`
- API: `https://soundlog.shop/api/soundlog`
본인 iPhone을 USB로 연결해 최신 코드를 직접 테스트할 때는 [iPhone 실기기 테스트 가이드](docs/deployment/IOS_PHYSICAL_DEVICE_TESTING.md)를 먼저 확인합니다. 실기기에서 `127.0.0.1`은 Mac이 아니라 iPhone 자신이므로, 로컬 서버를 사용할 때는 Mac의 LAN IP를 앱 빌드 환경변수로 지정해야 합니다.

- API: `https://api.soundlog.shop`
- 개인정보 처리방침: `https://api.soundlog.shop/legal/privacy`
- 서비스 이용약관: `https://api.soundlog.shop/legal/terms`
- 고객지원: `https://api.soundlog.shop/support`
- API source: `server`
- auth: Soundlog 자체 이메일/비밀번호 로그인
- iOS/Android: HTTPS API만 사용
Expand All @@ -67,7 +67,7 @@ Mock API로 되돌리는 런타임 경로는 제거했습니다. 화면 상태
API origin이 최신 서버인지 확인하려면 아래처럼 실행합니다.

```bash
SOUNDLOG_API_ORIGIN=http://<EC2_HOST>:4000 npm run check:api-origin
SOUNDLOG_API_ORIGIN=https://api.soundlog.shop npm run check:api-origin
```

이 검사는 로그인 필수 API도 함께 확인하므로 `SOUNDLOG_CHECK_EMAIL`,
Expand All @@ -80,7 +80,7 @@ Android 지인 테스트용 내부 배포 빌드는 아래 명령으로 생성
npx eas build --profile preview --platform android
```

iOS는 TestFlight 또는 ad hoc 기기 등록이 필요합니다. App Store/TestFlight에 올릴 production profile도 현재는 `https://soundlog.shop/api/soundlog`을 사용합니다.
iOS는 TestFlight 또는 ad hoc 기기 등록이 필요합니다. App Store/TestFlight에 올릴 production profile도 현재는 `https://api.soundlog.shop`을 사용합니다.

## 문서

Expand All @@ -93,7 +93,8 @@ iOS는 TestFlight 또는 ad hoc 기기 등록이 필요합니다. App Store/Test
- [UI 피드백 루프 운영 문서](docs/codex/UI_FEEDBACK_LOOP.md)
- [개발용 테스트 매니저](docs/codex/TEST_MANAGER.md)
- [PR 전용 개발 흐름](docs/codex/PR_ONLY_WORKFLOW.md)
- [soundlog.shop 배포 설정](docs/deployment/SOUNDLOG_SHOP_DOMAIN.md)
- [GCP 앱 배포 도메인 설정](docs/deployment/SOUNDLOG_SHOP_DOMAIN.md)
- [iPhone 실기기 테스트 가이드](docs/deployment/IOS_PHYSICAL_DEVICE_TESTING.md)
- [Mock Server 안내](src/mock-server/README.md)

## Codex로 개발할 때
Expand Down
96 changes: 94 additions & 2 deletions app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const baseConfig = {
ios: {
infoPlist: {
CFBundleAllowMixedLocalizations: true,
ITSAppUsesNonExemptEncryption: false,
NSCameraUsageDescription:
'Soundlog가 여행 순간을 사진으로 기록하기 위해 카메라 권한이 필요합니다.',
NSFaceIDUsageDescription:
Expand All @@ -20,8 +21,77 @@ const baseConfig = {
NSPhotoLibraryUsageDescription:
'Soundlog가 리캡 이미지를 저장하기 위해 사진 접근 권한이 필요합니다.',
},
supportsTablet: true,
supportsTablet: false,
bundleIdentifier: 'com.mannomi.soundlog',
// Declares data actually collected and linked to the account (name, email,
// account ID, precise location, photos, user-entered content, and in-app
// interactions). None of it is used for cross-app/company ad tracking, so
// NSPrivacyTracking stays false. The
// NSPrivacyAccessedAPITypes entries mirror the reasons already present in the
// generated ios/Soundlog/PrivacyInfo.xcprivacy (UserDefaults, FileTimestamp,
// SystemBootTime) so they survive `expo prebuild --clean`.
privacyManifests: {
NSPrivacyTracking: false,
NSPrivacyTrackingDomains: [],
NSPrivacyCollectedDataTypes: [
{
NSPrivacyCollectedDataType: 'NSPrivacyCollectedDataTypeEmailAddress',
NSPrivacyCollectedDataTypeLinked: true,
NSPrivacyCollectedDataTypeTracking: false,
NSPrivacyCollectedDataTypePurposes: ['NSPrivacyCollectedDataTypePurposeAppFunctionality'],
},
{
NSPrivacyCollectedDataType: 'NSPrivacyCollectedDataTypeName',
NSPrivacyCollectedDataTypeLinked: true,
NSPrivacyCollectedDataTypeTracking: false,
NSPrivacyCollectedDataTypePurposes: ['NSPrivacyCollectedDataTypePurposeAppFunctionality'],
},
{
NSPrivacyCollectedDataType: 'NSPrivacyCollectedDataTypeUserID',
NSPrivacyCollectedDataTypeLinked: true,
NSPrivacyCollectedDataTypeTracking: false,
NSPrivacyCollectedDataTypePurposes: ['NSPrivacyCollectedDataTypePurposeAppFunctionality'],
},
{
NSPrivacyCollectedDataType: 'NSPrivacyCollectedDataTypePreciseLocation',
NSPrivacyCollectedDataTypeLinked: true,
NSPrivacyCollectedDataTypeTracking: false,
NSPrivacyCollectedDataTypePurposes: ['NSPrivacyCollectedDataTypePurposeAppFunctionality'],
},
{
NSPrivacyCollectedDataType: 'NSPrivacyCollectedDataTypePhotosorVideos',
NSPrivacyCollectedDataTypeLinked: true,
NSPrivacyCollectedDataTypeTracking: false,
NSPrivacyCollectedDataTypePurposes: ['NSPrivacyCollectedDataTypePurposeAppFunctionality'],
},
{
NSPrivacyCollectedDataType: 'NSPrivacyCollectedDataTypeOtherUserContent',
NSPrivacyCollectedDataTypeLinked: true,
NSPrivacyCollectedDataTypeTracking: false,
NSPrivacyCollectedDataTypePurposes: ['NSPrivacyCollectedDataTypePurposeAppFunctionality'],
},
{
NSPrivacyCollectedDataType: 'NSPrivacyCollectedDataTypeProductInteraction',
NSPrivacyCollectedDataTypeLinked: true,
NSPrivacyCollectedDataTypeTracking: false,
NSPrivacyCollectedDataTypePurposes: ['NSPrivacyCollectedDataTypePurposeAnalytics'],
},
],
NSPrivacyAccessedAPITypes: [
{
NSPrivacyAccessedAPIType: 'NSPrivacyAccessedAPICategoryUserDefaults',
NSPrivacyAccessedAPITypeReasons: ['CA92.1'],
},
{
NSPrivacyAccessedAPIType: 'NSPrivacyAccessedAPICategoryFileTimestamp',
NSPrivacyAccessedAPITypeReasons: ['C617.1'],
},
{
NSPrivacyAccessedAPIType: 'NSPrivacyAccessedAPICategorySystemBootTime',
NSPrivacyAccessedAPITypeReasons: ['35F9.1'],
},
],
},
},
android: {
package: 'com.mannomi.soundlog',
Expand Down Expand Up @@ -58,13 +128,22 @@ const baseConfig = {
{
cameraPermission:
'Soundlog가 여행 순간을 사진으로 기록하기 위해 카메라 권한이 필요합니다.',
microphonePermission: false,
recordAudioAndroid: false,
},
],
[
'expo-location',
{
locationWhenInUsePermission:
'Soundlog가 현재 장소에 맞는 음악을 추천하고 여행 순간의 위치를 기록하기 위해 위치 권한이 필요합니다.',
locationAlwaysAndWhenInUsePermission: false,
locationAlwaysPermission: false,
motionUsagePermission: false,
isIosBackgroundLocationEnabled: false,
isAndroidBackgroundLocationEnabled: false,
isAndroidForegroundServiceEnabled: false,
isAndroidMotionActivityEnabled: false,
},
],
'expo-sharing',
Expand All @@ -79,7 +158,20 @@ const baseConfig = {
],
'expo-secure-store',
'expo-image',
['expo-build-properties', {}],
// Pin Android SDK versions explicitly instead of relying on the Expo SDK
// template default, so Play Store's targetSdk requirement doesn't silently
// drift on an SDK upgrade. Values match the current expo-modules-core
// default for Expo SDK 56 (confirmed in
// node_modules/expo-modules-core/expo-module-gradle-plugin/.../ProjectConfiguration.kt).
[
'expo-build-properties',
{
android: {
compileSdkVersion: 36,
targetSdkVersion: 36,
},
},
],
],
experiments: {
typedRoutes: true,
Expand Down
2 changes: 1 addition & 1 deletion app/(tabs)/my.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export default function MyScreen() {
onPress={() =>
router.push({
pathname: '/recap',
params: { view: 'all' },
params: { view: 'mine' },
} as never)
}
/>
Expand Down
27 changes: 13 additions & 14 deletions app/auth/callback.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Feather } from '@expo/vector-icons';
import { router } from 'expo-router';
import { Pressable, View } from 'react-native';

import { AppText } from '@/components/AppText';
import { IconButton } from '@/components/IconButton';
import { PageHeader } from '@/components/PageHeader';
import { Screen } from '@/components/Screen';
import { SectionTitle } from '@/components/SectionTitle';
import { SettingsRow } from '@/components/SettingsRow';

export default function AuthCallbackScreen() {
return (
Expand All @@ -20,22 +19,22 @@ export default function AuthCallbackScreen() {
onPress={() => router.replace('/auth/login' as never)}
/>
}
title="로그인 연결"
title="로그인"
/>

<View className="mt-10">
<SectionTitle title="계정 연결 상태" />
<SettingsRow
description="현재 앱은 이메일 계정 로그인을 사용합니다. 이전 로그인 링크는 다시 사용할 수 없어요."
icon="alert-circle"
label="연결 정보를 확인하지 못했어요"
/>
<View className="mt-24 items-center px-5">
<View className="h-16 w-16 items-center justify-center rounded-full bg-white/10">
<Feather color="#FFFFFF" name="alert-circle" size={28} />
</View>
<AppText className="mt-6 text-center text-2xl font-semibold text-white">
로그인을 완료하지 못했어요
</AppText>
<AppText className="mt-3 text-center text-sm leading-6 text-white/65">
로그인 정보가 만료되었거나 올바르지 않습니다.{`\n`}이메일과
비밀번호를 다시 확인해주세요.
</AppText>
</View>

<AppText className="mt-6 text-sm leading-6 text-white/52">
로그인 화면에서 이메일과 비밀번호를 입력해 다시 진행해주세요.
</AppText>

<Pressable
accessibilityRole="button"
className="mt-auto h-14 items-center justify-center rounded-xl bg-soundlog-lime"
Expand Down
Loading
Loading