update flow and auth model
This commit is contained in:
48
App.tsx
48
App.tsx
@@ -1,28 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* App Entry Point
|
||||||
|
*
|
||||||
|
* Main application component with authentication routing.
|
||||||
|
* Shows loading screen while restoring auth state.
|
||||||
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
import { NavigationContainer } from '@react-navigation/native';
|
||||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||||
import { StyleSheet, View, ActivityIndicator } from 'react-native';
|
import { StyleSheet, View, ActivityIndicator, Text } from 'react-native';
|
||||||
import TabNavigator from './src/navigation/TabNavigator';
|
import TabNavigator from './src/navigation/TabNavigator';
|
||||||
import AuthNavigator from './src/navigation/AuthNavigator';
|
import AuthNavigator from './src/navigation/AuthNavigator';
|
||||||
import { AuthProvider, useAuth } from './src/context/AuthContext';
|
import { AuthProvider, useAuth } from './src/context/AuthContext';
|
||||||
import { colors } from './src/theme/colors';
|
import { colors } from './src/theme/colors';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loading screen shown while restoring auth state
|
||||||
|
*/
|
||||||
|
function LoadingScreen() {
|
||||||
|
return (
|
||||||
|
<View style={styles.loadingContainer}>
|
||||||
|
<ActivityIndicator size="large" color={colors.nautical.teal} />
|
||||||
|
<Text style={styles.loadingText}>Loading...</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main app content with auth-based routing
|
||||||
|
*/
|
||||||
function AppContent() {
|
function AppContent() {
|
||||||
const { user } = useAuth();
|
const { user, isInitializing } = useAuth();
|
||||||
|
|
||||||
|
// Show loading screen while restoring auth state
|
||||||
|
if (isInitializing) {
|
||||||
|
return <LoadingScreen />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationContainer>
|
<NavigationContainer>
|
||||||
<StatusBar style="auto" />
|
<StatusBar style="auto" />
|
||||||
{user ? (
|
{user ? <TabNavigator /> : <AuthNavigator />}
|
||||||
<TabNavigator />
|
|
||||||
) : (
|
|
||||||
<AuthNavigator />
|
|
||||||
)}
|
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root App component
|
||||||
|
*/
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<GestureHandlerRootView style={styles.container}>
|
<GestureHandlerRootView style={styles.container}>
|
||||||
@@ -42,7 +68,11 @@ const styles = StyleSheet.create({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: colors.sentinel.background,
|
backgroundColor: colors.flow.backgroundGradientStart,
|
||||||
|
},
|
||||||
|
loadingText: {
|
||||||
|
marginTop: 16,
|
||||||
|
fontSize: 16,
|
||||||
|
color: colors.flow.textSecondary,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
95
README.md
95
README.md
@@ -44,8 +44,35 @@ Sentinel is a mobile application that helps users securely manage their digital
|
|||||||
- **Framework**: React Native (Expo SDK 52)
|
- **Framework**: React Native (Expo SDK 52)
|
||||||
- **Language**: TypeScript
|
- **Language**: TypeScript
|
||||||
- **Navigation**: React Navigation (Bottom Tabs)
|
- **Navigation**: React Navigation (Bottom Tabs)
|
||||||
- **Icons**: @expo/vector-icons (Feather, Ionicons, MaterialCommunityIcons, FontAwesome5)
|
- **Icons**: @expo/vector-icons (Feather, Ionicons, FontAwesome5)
|
||||||
- **Styling**: Custom nautical theme with gradients
|
- **Styling**: Custom nautical theme with gradients
|
||||||
|
- **State Management**: React Context (AuthContext)
|
||||||
|
- **Storage**: AsyncStorage for auth persistence
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The application uses a centralized configuration file located at `src/config/index.ts`.
|
||||||
|
|
||||||
|
### Key Configuration Options
|
||||||
|
|
||||||
|
| Option | Description | Default |
|
||||||
|
|--------|-------------|---------|
|
||||||
|
| `NO_BACKEND_MODE` | Use mock data instead of real backend | `false` |
|
||||||
|
| `DEBUG_MODE` | Enable API debug logging | `true` |
|
||||||
|
| `API_BASE_URL` | Backend API server URL | `http://localhost:8000` |
|
||||||
|
| `API_TIMEOUT` | Request timeout (ms) | `30000` |
|
||||||
|
|
||||||
|
### API Endpoints
|
||||||
|
|
||||||
|
All backend API routes are defined in `API_ENDPOINTS`:
|
||||||
|
- **AUTH**: `/login`, `/register`
|
||||||
|
- **ASSETS**: `/assets/get`, `/assets/create`, `/assets/claim`, `/assets/assign`
|
||||||
|
- **AI**: `/ai/proxy`
|
||||||
|
- **ADMIN**: `/admin/declare-guale`
|
||||||
|
|
||||||
|
### Environment Setup
|
||||||
|
|
||||||
|
For development, you may need to modify `API_BASE_URL` in the config file to match your backend server address.
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
@@ -56,14 +83,27 @@ src/
|
|||||||
│ ├── BiometricModal.tsx
|
│ ├── BiometricModal.tsx
|
||||||
│ ├── Icons.tsx
|
│ ├── Icons.tsx
|
||||||
│ └── VaultDoorAnimation.tsx
|
│ └── VaultDoorAnimation.tsx
|
||||||
|
├── config/
|
||||||
|
│ └── index.ts # Centralized configuration
|
||||||
|
├── context/
|
||||||
|
│ └── AuthContext.tsx # Authentication state management
|
||||||
├── navigation/
|
├── navigation/
|
||||||
│ └── TabNavigator.tsx
|
│ ├── AuthNavigator.tsx # Login/Register navigation
|
||||||
|
│ └── TabNavigator.tsx # Main app navigation
|
||||||
├── screens/
|
├── screens/
|
||||||
│ ├── FlowScreen.tsx
|
│ ├── FlowScreen.tsx # AI chat interface
|
||||||
│ ├── VaultScreen.tsx
|
│ ├── VaultScreen.tsx
|
||||||
│ ├── SentinelScreen.tsx
|
│ ├── SentinelScreen.tsx
|
||||||
│ ├── HeritageScreen.tsx
|
│ ├── HeritageScreen.tsx
|
||||||
│ └── MeScreen.tsx
|
│ ├── MeScreen.tsx
|
||||||
|
│ ├── LoginScreen.tsx
|
||||||
|
│ └── RegisterScreen.tsx
|
||||||
|
├── services/
|
||||||
|
│ ├── index.ts # Service exports
|
||||||
|
│ ├── ai.service.ts # AI API integration
|
||||||
|
│ ├── auth.service.ts # Authentication API
|
||||||
|
│ ├── assets.service.ts # Asset management API
|
||||||
|
│ └── admin.service.ts # Admin operations API
|
||||||
├── theme/
|
├── theme/
|
||||||
│ └── colors.ts
|
│ └── colors.ts
|
||||||
└── types/
|
└── types/
|
||||||
@@ -80,6 +120,15 @@ assets/
|
|||||||
└── captain-avatar.svg # Avatar placeholder
|
└── captain-avatar.svg # Avatar placeholder
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Services
|
||||||
|
|
||||||
|
The application uses a modular service architecture for API communication:
|
||||||
|
|
||||||
|
- **AuthService**: User authentication (login, register)
|
||||||
|
- **AIService**: AI conversation proxy with support for text and image input
|
||||||
|
- **AssetsService**: Digital asset management
|
||||||
|
- **AdminService**: Administrative operations
|
||||||
|
|
||||||
## Icons & Branding
|
## Icons & Branding
|
||||||
|
|
||||||
The Sentinel brand uses a nautical anchor-and-star logo on a teal (#459E9E) background.
|
The Sentinel brand uses a nautical anchor-and-star logo on a teal (#459E9E) background.
|
||||||
@@ -163,8 +212,44 @@ Sentinel 是一款帮助用户安全管理数字遗产的移动应用程序。
|
|||||||
- **框架**: React Native (Expo SDK 52)
|
- **框架**: React Native (Expo SDK 52)
|
||||||
- **语言**: TypeScript
|
- **语言**: TypeScript
|
||||||
- **导航**: React Navigation (底部标签)
|
- **导航**: React Navigation (底部标签)
|
||||||
- **图标**: @expo/vector-icons
|
- **图标**: @expo/vector-icons (Feather, Ionicons, FontAwesome5)
|
||||||
- **样式**: 自定义航海主题配渐变
|
- **样式**: 自定义航海主题配渐变
|
||||||
|
- **状态管理**: React Context (AuthContext)
|
||||||
|
- **存储**: AsyncStorage 用于认证持久化
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
应用使用位于 `src/config/index.ts` 的集中配置文件。
|
||||||
|
|
||||||
|
### 主要配置项
|
||||||
|
|
||||||
|
| 选项 | 说明 | 默认值 |
|
||||||
|
|------|------|--------|
|
||||||
|
| `NO_BACKEND_MODE` | 使用模拟数据而非真实后端 | `false` |
|
||||||
|
| `DEBUG_MODE` | 启用 API 调试日志 | `true` |
|
||||||
|
| `API_BASE_URL` | 后端 API 服务器地址 | `http://localhost:8000` |
|
||||||
|
| `API_TIMEOUT` | 请求超时时间(毫秒) | `30000` |
|
||||||
|
|
||||||
|
### API 端点
|
||||||
|
|
||||||
|
所有后端 API 路由定义在 `API_ENDPOINTS` 中:
|
||||||
|
- **AUTH**: `/login`, `/register`
|
||||||
|
- **ASSETS**: `/assets/get`, `/assets/create`, `/assets/claim`, `/assets/assign`
|
||||||
|
- **AI**: `/ai/proxy`
|
||||||
|
- **ADMIN**: `/admin/declare-guale`
|
||||||
|
|
||||||
|
### 环境配置
|
||||||
|
|
||||||
|
开发时,您可能需要修改配置文件中的 `API_BASE_URL` 以匹配您的后端服务器地址。
|
||||||
|
|
||||||
|
## 服务层
|
||||||
|
|
||||||
|
应用使用模块化的服务架构进行 API 通信:
|
||||||
|
|
||||||
|
- **AuthService**: 用户认证(登录、注册)
|
||||||
|
- **AIService**: AI 对话代理,支持文本和图片输入
|
||||||
|
- **AssetsService**: 数字资产管理
|
||||||
|
- **AdminService**: 管理员操作
|
||||||
|
|
||||||
## 运行项目
|
## 运行项目
|
||||||
|
|
||||||
|
|||||||
56
package-lock.json
generated
56
package-lock.json
generated
@@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/metro-runtime": "~4.0.1",
|
"@expo/metro-runtime": "~4.0.1",
|
||||||
"@expo/vector-icons": "~14.0.4",
|
"@expo/vector-icons": "~14.0.4",
|
||||||
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||||
"@react-navigation/bottom-tabs": "^6.6.1",
|
"@react-navigation/bottom-tabs": "^6.6.1",
|
||||||
"@react-navigation/native": "^6.1.18",
|
"@react-navigation/native": "^6.1.18",
|
||||||
"@react-navigation/native-stack": "^6.11.0",
|
"@react-navigation/native-stack": "^6.11.0",
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
"expo-constants": "~17.0.8",
|
"expo-constants": "~17.0.8",
|
||||||
"expo-font": "~13.0.4",
|
"expo-font": "~13.0.4",
|
||||||
"expo-haptics": "~14.0.0",
|
"expo-haptics": "~14.0.0",
|
||||||
|
"expo-image-picker": "^17.0.10",
|
||||||
"expo-linear-gradient": "~14.0.2",
|
"expo-linear-gradient": "~14.0.2",
|
||||||
"expo-status-bar": "~2.0.0",
|
"expo-status-bar": "~2.0.0",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
@@ -3275,6 +3277,18 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-native-async-storage/async-storage": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"merge-options": "^3.0.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react-native": "^0.0.0-0 || >=0.65 <1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@react-native/assets-registry": {
|
"node_modules/@react-native/assets-registry": {
|
||||||
"version": "0.76.9",
|
"version": "0.76.9",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.9.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.9.tgz",
|
||||||
@@ -5722,6 +5736,27 @@
|
|||||||
"expo": "*"
|
"expo": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/expo-image-loader": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-nKs/xnOGw6ACb4g26xceBD57FKLFkSwEUTDXEDF3Gtcu3MqF3ZIYd3YM+sSb1/z9AKV1dYT7rMSGVNgsveXLIQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"expo": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/expo-image-picker": {
|
||||||
|
"version": "17.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-17.0.10.tgz",
|
||||||
|
"integrity": "sha512-a2xrowp2trmvXyUWgX3O6Q2rZaa2C59AqivKI7+bm+wLvMfTEbZgldLX4rEJJhM8xtmEDTNU+lzjtObwzBRGaw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"expo-image-loader": "~6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"expo": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/expo-linear-gradient": {
|
"node_modules/expo-linear-gradient": {
|
||||||
"version": "14.0.2",
|
"version": "14.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/expo-linear-gradient/-/expo-linear-gradient-14.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/expo-linear-gradient/-/expo-linear-gradient-14.0.2.tgz",
|
||||||
@@ -6712,6 +6747,15 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-plain-obj": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-plain-object": {
|
"node_modules/is-plain-object": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||||
@@ -7591,6 +7635,18 @@
|
|||||||
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
|
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/merge-options": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-plain-obj": "^2.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/merge-stream": {
|
"node_modules/merge-stream": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/metro-runtime": "~4.0.1",
|
"@expo/metro-runtime": "~4.0.1",
|
||||||
"@expo/vector-icons": "~14.0.4",
|
"@expo/vector-icons": "~14.0.4",
|
||||||
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||||
"@react-navigation/bottom-tabs": "^6.6.1",
|
"@react-navigation/bottom-tabs": "^6.6.1",
|
||||||
"@react-navigation/native": "^6.1.18",
|
"@react-navigation/native": "^6.1.18",
|
||||||
"@react-navigation/native-stack": "^6.11.0",
|
"@react-navigation/native-stack": "^6.11.0",
|
||||||
@@ -19,6 +20,7 @@
|
|||||||
"expo-constants": "~17.0.8",
|
"expo-constants": "~17.0.8",
|
||||||
"expo-font": "~13.0.4",
|
"expo-font": "~13.0.4",
|
||||||
"expo-haptics": "~14.0.0",
|
"expo-haptics": "~14.0.0",
|
||||||
|
"expo-image-picker": "^17.0.10",
|
||||||
"expo-linear-gradient": "~14.0.2",
|
"expo-linear-gradient": "~14.0.2",
|
||||||
"expo-status-bar": "~2.0.0",
|
"expo-status-bar": "~2.0.0",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
|
|||||||
146
src/config/index.ts
Normal file
146
src/config/index.ts
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
/**
|
||||||
|
* Application Configuration
|
||||||
|
*
|
||||||
|
* This file contains all configuration settings for the frontend application.
|
||||||
|
* Centralized configuration makes it easier to manage environment-specific settings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Environment Configuration
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true to use mock data instead of real backend calls.
|
||||||
|
* Useful for development and testing without a running backend.
|
||||||
|
*/
|
||||||
|
export const NO_BACKEND_MODE = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable debug logging for API calls
|
||||||
|
*/
|
||||||
|
export const DEBUG_MODE = true;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// API Configuration
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base URL for the backend API server
|
||||||
|
*/
|
||||||
|
export const API_BASE_URL = 'http://localhost:8000';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API request timeout in milliseconds
|
||||||
|
*/
|
||||||
|
export const API_TIMEOUT = 30000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Endpoints
|
||||||
|
* All backend API routes are defined here for easy reference and maintenance.
|
||||||
|
*/
|
||||||
|
export const API_ENDPOINTS = {
|
||||||
|
// Authentication
|
||||||
|
AUTH: {
|
||||||
|
LOGIN: '/login',
|
||||||
|
REGISTER: '/register',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Assets Management
|
||||||
|
ASSETS: {
|
||||||
|
GET: '/assets/get',
|
||||||
|
CREATE: '/assets/create',
|
||||||
|
CLAIM: '/assets/claim',
|
||||||
|
ASSIGN: '/assets/assign',
|
||||||
|
},
|
||||||
|
|
||||||
|
// AI Services
|
||||||
|
AI: {
|
||||||
|
PROXY: '/ai/proxy',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Admin Operations
|
||||||
|
ADMIN: {
|
||||||
|
DECLARE_GUALE: '/admin/declare-guale',
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Helper Functions
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build full API URL from endpoint
|
||||||
|
* @param endpoint - API endpoint path (e.g., '/login')
|
||||||
|
* @returns Full URL (e.g., 'http://192.168.56.103:8000/login')
|
||||||
|
*/
|
||||||
|
export function buildApiUrl(endpoint: string): string {
|
||||||
|
return `${API_BASE_URL}${endpoint}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default headers for API requests
|
||||||
|
* @param token - Optional JWT token for authenticated requests
|
||||||
|
* @returns Headers object
|
||||||
|
*/
|
||||||
|
export function getApiHeaders(token?: string): Record<string, string> {
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
headers['Authorization'] = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log API debug information
|
||||||
|
* Only logs when DEBUG_MODE is enabled
|
||||||
|
*/
|
||||||
|
export function logApiDebug(label: string, data: unknown): void {
|
||||||
|
if (DEBUG_MODE) {
|
||||||
|
console.log(`[API Debug] ${label}:`, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Mock User Configuration (for NO_BACKEND_MODE)
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export const MOCK_CONFIG = {
|
||||||
|
USER: {
|
||||||
|
id: 999,
|
||||||
|
username: 'MockCaptain',
|
||||||
|
public_key: 'mock_public_key',
|
||||||
|
is_admin: true,
|
||||||
|
guale: false,
|
||||||
|
tier: 'premium',
|
||||||
|
tier_expires_at: '2026-12-31T23:59:59Z',
|
||||||
|
last_active_at: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
ACCESS_TOKEN: 'mock_access_token',
|
||||||
|
RESPONSE_DELAY: 200, // milliseconds
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// AI Service Configuration
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export const AI_CONFIG = {
|
||||||
|
/**
|
||||||
|
* Default system prompt for AI conversations
|
||||||
|
*/
|
||||||
|
DEFAULT_SYSTEM_PROMPT: 'You are a helpful journal assistant. Help the user reflect on their thoughts and feelings.',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock response delay in milliseconds (for NO_BACKEND_MODE)
|
||||||
|
*/
|
||||||
|
MOCK_RESPONSE_DELAY: 500,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Export Type Definitions
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export type ApiEndpoint = typeof API_ENDPOINTS;
|
||||||
@@ -1,30 +1,118 @@
|
|||||||
import React, { createContext, useContext, useState, ReactNode } from 'react';
|
/**
|
||||||
|
* AuthContext - Authentication State Management
|
||||||
|
*
|
||||||
|
* Provides authentication state and methods throughout the app.
|
||||||
|
* Uses AsyncStorage for persistent login state.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||||
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import { User, LoginRequest, RegisterRequest } from '../types';
|
import { User, LoginRequest, RegisterRequest } from '../types';
|
||||||
import { authService } from '../services/auth.service';
|
import { authService } from '../services/auth.service';
|
||||||
import { Alert } from 'react-native';
|
|
||||||
|
// =============================================================================
|
||||||
|
// Type Definitions
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
interface AuthContextType {
|
interface AuthContextType {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
token: string | null;
|
token: string | null;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
isInitializing: boolean;
|
||||||
signIn: (credentials: LoginRequest) => Promise<void>;
|
signIn: (credentials: LoginRequest) => Promise<void>;
|
||||||
signUp: (data: RegisterRequest) => Promise<void>;
|
signUp: (data: RegisterRequest) => Promise<void>;
|
||||||
signOut: () => void;
|
signOut: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Storage keys
|
||||||
|
const STORAGE_KEYS = {
|
||||||
|
TOKEN: '@auth_token',
|
||||||
|
USER: '@auth_user',
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Context
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Provider Component
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||||
const [user, setUser] = useState<User | null>(null);
|
const [user, setUser] = useState<User | null>(null);
|
||||||
const [token, setToken] = useState<string | null>(null);
|
const [token, setToken] = useState<string | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isInitializing, setIsInitializing] = useState(true);
|
||||||
|
|
||||||
|
// Load saved auth state on app start
|
||||||
|
useEffect(() => {
|
||||||
|
loadStoredAuth();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load stored authentication from AsyncStorage
|
||||||
|
*/
|
||||||
|
const loadStoredAuth = async () => {
|
||||||
|
try {
|
||||||
|
const [storedToken, storedUser] = await Promise.all([
|
||||||
|
AsyncStorage.getItem(STORAGE_KEYS.TOKEN),
|
||||||
|
AsyncStorage.getItem(STORAGE_KEYS.USER),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (storedToken && storedUser) {
|
||||||
|
setToken(storedToken);
|
||||||
|
setUser(JSON.parse(storedUser));
|
||||||
|
console.log('[Auth] Restored session for user:', JSON.parse(storedUser).username);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Auth] Failed to load stored auth:', error);
|
||||||
|
} finally {
|
||||||
|
setIsInitializing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save authentication to AsyncStorage
|
||||||
|
*/
|
||||||
|
const saveAuth = async (authToken: string, authUser: User) => {
|
||||||
|
try {
|
||||||
|
await Promise.all([
|
||||||
|
AsyncStorage.setItem(STORAGE_KEYS.TOKEN, authToken),
|
||||||
|
AsyncStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(authUser)),
|
||||||
|
]);
|
||||||
|
console.log('[Auth] Session saved for user:', authUser.username);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Auth] Failed to save auth:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear authentication from AsyncStorage
|
||||||
|
*/
|
||||||
|
const clearAuth = async () => {
|
||||||
|
try {
|
||||||
|
await Promise.all([
|
||||||
|
AsyncStorage.removeItem(STORAGE_KEYS.TOKEN),
|
||||||
|
AsyncStorage.removeItem(STORAGE_KEYS.USER),
|
||||||
|
]);
|
||||||
|
console.log('[Auth] Session cleared');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Auth] Failed to clear auth:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign in with username and password
|
||||||
|
*/
|
||||||
const signIn = async (credentials: LoginRequest) => {
|
const signIn = async (credentials: LoginRequest) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await authService.login(credentials);
|
const response = await authService.login(credentials);
|
||||||
setToken(response.access_token);
|
setToken(response.access_token);
|
||||||
setUser(response.user);
|
setUser(response.user);
|
||||||
|
await saveAuth(response.access_token, response.user);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -32,6 +120,9 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign up and automatically sign in
|
||||||
|
*/
|
||||||
const signUp = async (data: RegisterRequest) => {
|
const signUp = async (data: RegisterRequest) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -45,18 +136,36 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign out and clear stored auth
|
||||||
|
*/
|
||||||
const signOut = () => {
|
const signOut = () => {
|
||||||
setUser(null);
|
setUser(null);
|
||||||
setToken(null);
|
setToken(null);
|
||||||
|
clearAuth();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={{ user, token, isLoading, signIn, signUp, signOut }}>
|
<AuthContext.Provider
|
||||||
|
value={{
|
||||||
|
user,
|
||||||
|
token,
|
||||||
|
isLoading,
|
||||||
|
isInitializing,
|
||||||
|
signIn,
|
||||||
|
signUp,
|
||||||
|
signOut
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Hook
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
export function useAuth() {
|
export function useAuth() {
|
||||||
const context = useContext(AuthContext);
|
const context = useContext(AuthContext);
|
||||||
if (context === undefined) {
|
if (context === undefined) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
79
src/services/admin.service.ts
Normal file
79
src/services/admin.service.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Admin Service
|
||||||
|
*
|
||||||
|
* Handles admin-only API operations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
NO_BACKEND_MODE,
|
||||||
|
API_ENDPOINTS,
|
||||||
|
MOCK_CONFIG,
|
||||||
|
buildApiUrl,
|
||||||
|
getApiHeaders,
|
||||||
|
logApiDebug,
|
||||||
|
} from '../config';
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Type Definitions
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export interface DeclareGualeRequest {
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeclareGualeResponse {
|
||||||
|
message: string;
|
||||||
|
username: string;
|
||||||
|
guale: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Admin Service
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export const adminService = {
|
||||||
|
/**
|
||||||
|
* Declare a user as deceased (guale)
|
||||||
|
* Admin only operation
|
||||||
|
* @param request - Username to declare as deceased
|
||||||
|
* @param token - JWT token for authentication (must be admin)
|
||||||
|
* @returns Success response
|
||||||
|
*/
|
||||||
|
async declareGuale(request: DeclareGualeRequest, token: string): Promise<DeclareGualeResponse> {
|
||||||
|
if (NO_BACKEND_MODE) {
|
||||||
|
logApiDebug('Declare Guale', 'Using mock mode');
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({
|
||||||
|
message: `User ${request.username} has been declared as deceased`,
|
||||||
|
username: request.username,
|
||||||
|
guale: true,
|
||||||
|
});
|
||||||
|
}, MOCK_CONFIG.RESPONSE_DELAY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = buildApiUrl(API_ENDPOINTS.ADMIN.DECLARE_GUALE);
|
||||||
|
logApiDebug('Declare Guale URL', url);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getApiHeaders(token),
|
||||||
|
body: JSON.stringify(request),
|
||||||
|
});
|
||||||
|
|
||||||
|
logApiDebug('Declare Guale Response Status', response.status);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.detail || 'Failed to declare user as deceased');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Declare guale error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
243
src/services/ai.service.ts
Normal file
243
src/services/ai.service.ts
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
/**
|
||||||
|
* AI Service
|
||||||
|
*
|
||||||
|
* Handles communication with the AI proxy endpoint for chat completions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
NO_BACKEND_MODE,
|
||||||
|
API_ENDPOINTS,
|
||||||
|
AI_CONFIG,
|
||||||
|
buildApiUrl,
|
||||||
|
getApiHeaders,
|
||||||
|
logApiDebug,
|
||||||
|
} from '../config';
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Type Definitions
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export interface AIMessage {
|
||||||
|
role: 'system' | 'user' | 'assistant';
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AIRequest {
|
||||||
|
messages: AIMessage[];
|
||||||
|
model?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AIResponse {
|
||||||
|
id: string;
|
||||||
|
object: string;
|
||||||
|
created: number;
|
||||||
|
model: string;
|
||||||
|
choices: Array<{
|
||||||
|
index: number;
|
||||||
|
message: AIMessage;
|
||||||
|
finish_reason: string;
|
||||||
|
}>;
|
||||||
|
usage: {
|
||||||
|
prompt_tokens: number;
|
||||||
|
completion_tokens: number;
|
||||||
|
total_tokens: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Mock Response Generator
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
const createMockResponse = (userMessage: string): AIResponse => {
|
||||||
|
return {
|
||||||
|
id: `mock-${Date.now()}`,
|
||||||
|
object: 'chat.completion',
|
||||||
|
created: Math.floor(Date.now() / 1000),
|
||||||
|
model: 'mock-model',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
message: {
|
||||||
|
role: 'assistant',
|
||||||
|
content: `I received your message: "${userMessage}". This is a mock response since the backend is not connected.`,
|
||||||
|
},
|
||||||
|
finish_reason: 'stop',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
usage: {
|
||||||
|
prompt_tokens: 10,
|
||||||
|
completion_tokens: 20,
|
||||||
|
total_tokens: 30,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// AI Service
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export const aiService = {
|
||||||
|
/**
|
||||||
|
* Send chat messages to the AI proxy
|
||||||
|
* @param messages - Array of chat messages
|
||||||
|
* @param token - JWT token for authentication
|
||||||
|
* @returns AI response
|
||||||
|
*/
|
||||||
|
async chat(messages: AIMessage[], token?: string): Promise<AIResponse> {
|
||||||
|
if (NO_BACKEND_MODE) {
|
||||||
|
logApiDebug('AI Chat', 'Using mock mode');
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const lastUserMessage = messages.filter((m) => m.role === 'user').pop();
|
||||||
|
resolve(createMockResponse(lastUserMessage?.content || 'Hello'));
|
||||||
|
}, AI_CONFIG.MOCK_RESPONSE_DELAY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = buildApiUrl(API_ENDPOINTS.AI.PROXY);
|
||||||
|
|
||||||
|
logApiDebug('AI Request', {
|
||||||
|
url,
|
||||||
|
hasToken: !!token,
|
||||||
|
messageCount: messages.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getApiHeaders(token),
|
||||||
|
body: JSON.stringify({ messages } as AIRequest),
|
||||||
|
});
|
||||||
|
|
||||||
|
logApiDebug('AI Response Status', response.status);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
logApiDebug('AI Error Response', errorText);
|
||||||
|
|
||||||
|
let errorDetail = 'AI request failed';
|
||||||
|
try {
|
||||||
|
const errorData = JSON.parse(errorText);
|
||||||
|
errorDetail = errorData.detail || errorDetail;
|
||||||
|
} catch {
|
||||||
|
errorDetail = errorText || errorDetail;
|
||||||
|
}
|
||||||
|
throw new Error(`${response.status}: ${errorDetail}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
logApiDebug('AI Success', {
|
||||||
|
id: data.id,
|
||||||
|
model: data.model,
|
||||||
|
choicesCount: data.choices?.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('AI proxy error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple helper for single message chat
|
||||||
|
* @param content - User message content
|
||||||
|
* @param token - JWT token for authentication
|
||||||
|
* @returns AI response text
|
||||||
|
*/
|
||||||
|
async sendMessage(content: string, token?: string): Promise<string> {
|
||||||
|
const messages: AIMessage[] = [
|
||||||
|
{
|
||||||
|
role: 'system',
|
||||||
|
content: AI_CONFIG.DEFAULT_SYSTEM_PROMPT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const response = await this.chat(messages, token);
|
||||||
|
return response.choices[0]?.message?.content || 'No response';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message with an image to AI for analysis
|
||||||
|
* @param content - User message content
|
||||||
|
* @param imageBase64 - Base64 encoded image data
|
||||||
|
* @param token - JWT token for authentication
|
||||||
|
* @returns AI response text
|
||||||
|
*/
|
||||||
|
async sendMessageWithImage(content: string, imageBase64: string, token?: string): Promise<string> {
|
||||||
|
if (NO_BACKEND_MODE) {
|
||||||
|
logApiDebug('AI Image Analysis', 'Using mock mode');
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve('This is a mock image analysis response. The image appears to show an interesting scene. In production, this would be analyzed by Gemini AI.');
|
||||||
|
}, AI_CONFIG.MOCK_RESPONSE_DELAY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = buildApiUrl(API_ENDPOINTS.AI.PROXY);
|
||||||
|
|
||||||
|
logApiDebug('AI Image Request', {
|
||||||
|
url,
|
||||||
|
hasToken: !!token,
|
||||||
|
hasImage: !!imageBase64,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gemini vision format - using multimodal content
|
||||||
|
const messages = [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: content,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'image_url',
|
||||||
|
image_url: {
|
||||||
|
url: `data:image/jpeg;base64,${imageBase64}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getApiHeaders(token),
|
||||||
|
body: JSON.stringify({ messages }),
|
||||||
|
});
|
||||||
|
|
||||||
|
logApiDebug('AI Image Response Status', response.status);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
logApiDebug('AI Image Error Response', errorText);
|
||||||
|
|
||||||
|
let errorDetail = 'AI image request failed';
|
||||||
|
try {
|
||||||
|
const errorData = JSON.parse(errorText);
|
||||||
|
errorDetail = errorData.detail || errorDetail;
|
||||||
|
} catch {
|
||||||
|
errorDetail = errorText || errorDetail;
|
||||||
|
}
|
||||||
|
throw new Error(`${response.status}: ${errorDetail}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
logApiDebug('AI Image Success', {
|
||||||
|
id: data.id,
|
||||||
|
model: data.model,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.choices[0]?.message?.content || 'No response';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('AI image proxy error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
243
src/services/assets.service.ts
Normal file
243
src/services/assets.service.ts
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
/**
|
||||||
|
* Assets Service
|
||||||
|
*
|
||||||
|
* Handles all asset-related API operations including CRUD and inheritance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
NO_BACKEND_MODE,
|
||||||
|
API_ENDPOINTS,
|
||||||
|
MOCK_CONFIG,
|
||||||
|
buildApiUrl,
|
||||||
|
getApiHeaders,
|
||||||
|
logApiDebug,
|
||||||
|
} from '../config';
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Type Definitions
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export interface Asset {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
author_id: number;
|
||||||
|
private_key_shard: string;
|
||||||
|
content_outer_encrypted: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssetCreate {
|
||||||
|
title: string;
|
||||||
|
private_key_shard: string;
|
||||||
|
content_inner_encrypted: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssetClaim {
|
||||||
|
asset_id: number;
|
||||||
|
private_key_shard: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssetClaimResponse {
|
||||||
|
asset_id: number;
|
||||||
|
title: string;
|
||||||
|
decrypted_content: string;
|
||||||
|
server_shard_key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssetAssign {
|
||||||
|
asset_id: number;
|
||||||
|
heir_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Mock Data
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
const MOCK_ASSETS: Asset[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: 'Mock Asset 1',
|
||||||
|
author_id: MOCK_CONFIG.USER.id,
|
||||||
|
private_key_shard: 'mock_shard_1',
|
||||||
|
content_outer_encrypted: 'mock_encrypted_content_1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: 'Mock Asset 2',
|
||||||
|
author_id: MOCK_CONFIG.USER.id,
|
||||||
|
private_key_shard: 'mock_shard_2',
|
||||||
|
content_outer_encrypted: 'mock_encrypted_content_2',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Assets Service
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export const assetsService = {
|
||||||
|
/**
|
||||||
|
* Get all assets for the current user
|
||||||
|
* @param token - JWT token for authentication
|
||||||
|
* @returns Array of user's assets
|
||||||
|
*/
|
||||||
|
async getMyAssets(token: string): Promise<Asset[]> {
|
||||||
|
if (NO_BACKEND_MODE) {
|
||||||
|
logApiDebug('Get Assets', 'Using mock mode');
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => resolve(MOCK_ASSETS), MOCK_CONFIG.RESPONSE_DELAY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = buildApiUrl(API_ENDPOINTS.ASSETS.GET);
|
||||||
|
logApiDebug('Get Assets URL', url);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: getApiHeaders(token),
|
||||||
|
});
|
||||||
|
|
||||||
|
logApiDebug('Get Assets Response Status', response.status);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.detail || 'Failed to fetch assets');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Get assets error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new asset
|
||||||
|
* @param asset - Asset creation data
|
||||||
|
* @param token - JWT token for authentication
|
||||||
|
* @returns Created asset
|
||||||
|
*/
|
||||||
|
async createAsset(asset: AssetCreate, token: string): Promise<Asset> {
|
||||||
|
if (NO_BACKEND_MODE) {
|
||||||
|
logApiDebug('Create Asset', 'Using mock mode');
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({
|
||||||
|
id: Date.now(),
|
||||||
|
title: asset.title,
|
||||||
|
author_id: MOCK_CONFIG.USER.id,
|
||||||
|
private_key_shard: asset.private_key_shard,
|
||||||
|
content_outer_encrypted: asset.content_inner_encrypted,
|
||||||
|
});
|
||||||
|
}, MOCK_CONFIG.RESPONSE_DELAY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = buildApiUrl(API_ENDPOINTS.ASSETS.CREATE);
|
||||||
|
logApiDebug('Create Asset URL', url);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getApiHeaders(token),
|
||||||
|
body: JSON.stringify(asset),
|
||||||
|
});
|
||||||
|
|
||||||
|
logApiDebug('Create Asset Response Status', response.status);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.detail || 'Failed to create asset');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Create asset error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Claim an inherited asset
|
||||||
|
* @param claim - Asset claim data
|
||||||
|
* @param token - JWT token for authentication
|
||||||
|
* @returns Claimed asset with decrypted content
|
||||||
|
*/
|
||||||
|
async claimAsset(claim: AssetClaim, token: string): Promise<AssetClaimResponse> {
|
||||||
|
if (NO_BACKEND_MODE) {
|
||||||
|
logApiDebug('Claim Asset', 'Using mock mode');
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({
|
||||||
|
asset_id: claim.asset_id,
|
||||||
|
title: 'Mock Claimed Asset',
|
||||||
|
decrypted_content: 'This is the decrypted content of the claimed asset.',
|
||||||
|
server_shard_key: 'mock_server_shard',
|
||||||
|
});
|
||||||
|
}, MOCK_CONFIG.RESPONSE_DELAY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = buildApiUrl(API_ENDPOINTS.ASSETS.CLAIM);
|
||||||
|
logApiDebug('Claim Asset URL', url);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getApiHeaders(token),
|
||||||
|
body: JSON.stringify(claim),
|
||||||
|
});
|
||||||
|
|
||||||
|
logApiDebug('Claim Asset Response Status', response.status);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.detail || 'Failed to claim asset');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Claim asset error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign an asset to an heir
|
||||||
|
* @param assignment - Asset assignment data
|
||||||
|
* @param token - JWT token for authentication
|
||||||
|
* @returns Success message
|
||||||
|
*/
|
||||||
|
async assignAsset(assignment: AssetAssign, token: string): Promise<{ message: string }> {
|
||||||
|
if (NO_BACKEND_MODE) {
|
||||||
|
logApiDebug('Assign Asset', 'Using mock mode');
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({ message: `Asset assigned to ${assignment.heir_name}` });
|
||||||
|
}, MOCK_CONFIG.RESPONSE_DELAY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = buildApiUrl(API_ENDPOINTS.ASSETS.ASSIGN);
|
||||||
|
logApiDebug('Assign Asset URL', url);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getApiHeaders(token),
|
||||||
|
body: JSON.stringify(assignment),
|
||||||
|
});
|
||||||
|
|
||||||
|
logApiDebug('Assign Asset Response Status', response.status);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.detail || 'Failed to assign asset');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Assign asset error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,47 +1,48 @@
|
|||||||
import { LoginRequest, LoginResponse, RegisterRequest, User } from '../types';
|
import { LoginRequest, LoginResponse, RegisterRequest, User } from '../types';
|
||||||
|
import {
|
||||||
const PLATFORM_URL = 'http://192.168.56.103:8000';
|
NO_BACKEND_MODE,
|
||||||
const no_backend_mode = true;
|
API_ENDPOINTS,
|
||||||
|
MOCK_CONFIG,
|
||||||
const MOCK_USER: User = {
|
buildApiUrl,
|
||||||
id: 999,
|
getApiHeaders,
|
||||||
username: 'MockCaptain',
|
logApiDebug,
|
||||||
public_key: 'mock_public_key',
|
} from '../config';
|
||||||
is_admin: true,
|
|
||||||
guale: false,
|
|
||||||
tier: 'premium',
|
|
||||||
tier_expires_at: '2026-12-31T23:59:59Z',
|
|
||||||
last_active_at: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const authService = {
|
export const authService = {
|
||||||
async login(credentials: LoginRequest): Promise<LoginResponse> {
|
async login(credentials: LoginRequest): Promise<LoginResponse> {
|
||||||
if (no_backend_mode) {
|
if (NO_BACKEND_MODE) {
|
||||||
console.log('No-Backend Mode: Simulating login...');
|
logApiDebug('Login', 'Using mock mode');
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolve({
|
resolve({
|
||||||
access_token: 'mock_access_token',
|
access_token: MOCK_CONFIG.ACCESS_TOKEN,
|
||||||
token_type: 'bearer',
|
token_type: 'bearer',
|
||||||
user: { ...MOCK_USER, username: credentials.username },
|
user: { ...MOCK_CONFIG.USER, username: credentials.username } as User,
|
||||||
});
|
});
|
||||||
}, 200);
|
}, MOCK_CONFIG.RESPONSE_DELAY);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const url = buildApiUrl(API_ENDPOINTS.AUTH.LOGIN);
|
||||||
|
logApiDebug('Login URL', url);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${PLATFORM_URL}/login`, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: getApiHeaders(),
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(credentials),
|
body: JSON.stringify(credentials),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
logApiDebug('Login Response Status', response.status);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Login failed');
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.detail || 'Login failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
return await response.json();
|
const data = await response.json();
|
||||||
|
logApiDebug('Login Success', { username: data.user?.username });
|
||||||
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Login error:', error);
|
console.error('Login error:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -49,29 +50,35 @@ export const authService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async register(data: RegisterRequest): Promise<User> {
|
async register(data: RegisterRequest): Promise<User> {
|
||||||
if (no_backend_mode) {
|
if (NO_BACKEND_MODE) {
|
||||||
console.log('No-Backend Mode: Simulating registration...');
|
logApiDebug('Register', 'Using mock mode');
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolve({ ...MOCK_USER, username: data.username });
|
resolve({ ...MOCK_CONFIG.USER, username: data.username } as User);
|
||||||
}, 200);
|
}, MOCK_CONFIG.RESPONSE_DELAY);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const url = buildApiUrl(API_ENDPOINTS.AUTH.REGISTER);
|
||||||
|
logApiDebug('Register URL', url);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${PLATFORM_URL}/register`, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: getApiHeaders(),
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
logApiDebug('Register Response Status', response.status);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
throw new Error(errorData.detail?.[0]?.msg || 'Registration failed');
|
throw new Error(errorData.detail?.[0]?.msg || errorData.detail || 'Registration failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
return await response.json();
|
const result = await response.json();
|
||||||
|
logApiDebug('Register Success', { username: result.username });
|
||||||
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Registration error:', error);
|
console.error('Registration error:', error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
25
src/services/index.ts
Normal file
25
src/services/index.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Services Index
|
||||||
|
*
|
||||||
|
* Central export for all API services.
|
||||||
|
* Import services from here for cleaner imports.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* import { authService, aiService, assetsService, adminService } from '../services';
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { authService } from './auth.service';
|
||||||
|
export { aiService, type AIMessage, type AIRequest, type AIResponse } from './ai.service';
|
||||||
|
export {
|
||||||
|
assetsService,
|
||||||
|
type Asset,
|
||||||
|
type AssetCreate,
|
||||||
|
type AssetClaim,
|
||||||
|
type AssetClaimResponse,
|
||||||
|
type AssetAssign
|
||||||
|
} from './assets.service';
|
||||||
|
export {
|
||||||
|
adminService,
|
||||||
|
type DeclareGualeRequest,
|
||||||
|
type DeclareGualeResponse
|
||||||
|
} from './admin.service';
|
||||||
Reference in New Issue
Block a user