dpv007 commited on
Commit
6054a2e
·
1 Parent(s): 7cf51ae
keystone-app/app.json CHANGED
@@ -16,10 +16,14 @@
16
  "photosPermission": "Allow KeyStone to access your photos and videos to back them up to your private cloud.",
17
  "savePhotosPermission": "Allow KeyStone to save photos.",
18
  "isAccessMediaLocationEnabled": true,
19
- "granularPermissions": ["photo", "video"]
 
 
 
20
  }
21
  ],
22
- "expo-router"
 
23
  ],
24
  "extra": {
25
  "eas": {
 
16
  "photosPermission": "Allow KeyStone to access your photos and videos to back them up to your private cloud.",
17
  "savePhotosPermission": "Allow KeyStone to save photos.",
18
  "isAccessMediaLocationEnabled": true,
19
+ "granularPermissions": [
20
+ "photo",
21
+ "video"
22
+ ]
23
  }
24
  ],
25
+ "expo-router",
26
+ "expo-font"
27
  ],
28
  "extra": {
29
  "eas": {
keystone-app/app/(tabs)/index.tsx CHANGED
@@ -19,7 +19,7 @@ import { Image } from 'expo-image';
19
  import * as FileSystem from 'expo-file-system/legacy';
20
  import { Video, ResizeMode } from 'expo-av';
21
  import * as Sharing from 'expo-sharing';
22
- import NetInfo, { NetInfoState } from '@react-native-community/netinfo';
23
  import { StatusBar } from 'expo-status-bar';
24
  import { MaterialIcons } from '@expo/vector-icons';
25
  import { format, isToday, isYesterday } from 'date-fns';
@@ -70,7 +70,7 @@ export default function PhotosScreen() {
70
  const [syncProgress, setSyncProgress] = useState(0);
71
  const [syncTotal, setSyncTotal] = useState(0);
72
  const [syncStatus, setSyncStatus] = useState<'idle' | 'syncing' | 'done'>('idle');
73
- const [netInfo, setNetInfo] = useState<NetInfoState | null>(null);
74
  const [allowCellular, setAllowCellular] = useState(false);
75
  const [itemsToBackup, setItemsToBackup] = useState(0);
76
 
@@ -80,10 +80,13 @@ export default function PhotosScreen() {
80
  // ── Network & Settings Listeners ──────────────────────────────
81
  useEffect(() => {
82
  // Listen to network changes
83
- const unsubscribeNet = NetInfo.addEventListener(state => {
84
  setNetInfo(state);
85
  });
86
 
 
 
 
87
  // Load cellular setting
88
  const loadSetting = async () => {
89
  try {
@@ -101,7 +104,7 @@ export default function PhotosScreen() {
101
  });
102
 
103
  return () => {
104
- unsubscribeNet();
105
  subscription.remove();
106
  };
107
  }, []);
@@ -193,7 +196,7 @@ export default function PhotosScreen() {
193
  return;
194
  }
195
 
196
- if (netInfo?.type === 'cellular' && !allowCellular) {
197
  Alert.alert('Waiting for Wi-Fi', 'Syncing on mobile data is disabled in Settings.');
198
  return;
199
  }
@@ -221,12 +224,12 @@ export default function PhotosScreen() {
221
  try {
222
  const info = await MediaLibrary.getAssetInfoAsync(asset);
223
  const localUri = info.localUri || asset.uri;
224
- const resp = await FileSystem.uploadAsync(`${BACKEND_URL}/upload`, localUri, {
225
  fieldName: 'file',
226
  httpMethod: 'POST',
227
  uploadType: FileSystem.FileSystemUploadType.MULTIPART,
228
  parameters: { creation_time: asset.creationTime.toString() },
229
- headers: { Authorization: `Bearer ${MOCK_TOKEN}` },
230
  });
231
  if (resp.status === 200) {
232
  synced = await addSyncedId(asset.id, synced);
@@ -318,7 +321,7 @@ export default function PhotosScreen() {
318
  return { text: 'Offline', icon: 'cloud-off', color: '#d93025', spinning: false };
319
  }
320
 
321
- if (netInfo.type === 'cellular' && !allowCellular && itemsToBackup > 0) {
322
  return { text: 'Waiting for Wi-Fi', icon: 'cloud-sync', color: '#f29900', spinning: false };
323
  }
324
 
@@ -447,7 +450,7 @@ export default function PhotosScreen() {
447
  <TouchableOpacity
448
  style={styles.syncIconContainer}
449
  onPress={syncAssets}
450
- disabled={isSyncing || (!netInfo?.isConnected) || (netInfo?.type === 'cellular' && !allowCellular) || itemsToBackup === 0}
451
  >
452
  {statusUI.spinning ? (
453
  <ActivityIndicator size="small" color={statusUI.color} />
 
19
  import * as FileSystem from 'expo-file-system/legacy';
20
  import { Video, ResizeMode } from 'expo-av';
21
  import * as Sharing from 'expo-sharing';
22
+ import * as Network from 'expo-network';
23
  import { StatusBar } from 'expo-status-bar';
24
  import { MaterialIcons } from '@expo/vector-icons';
25
  import { format, isToday, isYesterday } from 'date-fns';
 
70
  const [syncProgress, setSyncProgress] = useState(0);
71
  const [syncTotal, setSyncTotal] = useState(0);
72
  const [syncStatus, setSyncStatus] = useState<'idle' | 'syncing' | 'done'>('idle');
73
+ const [netInfo, setNetInfo] = useState<Network.NetworkState | null>(null);
74
  const [allowCellular, setAllowCellular] = useState(false);
75
  const [itemsToBackup, setItemsToBackup] = useState(0);
76
 
 
80
  // ── Network & Settings Listeners ──────────────────────────────
81
  useEffect(() => {
82
  // Listen to network changes
83
+ const subscriptionNet = Network.addNetworkStateListener(state => {
84
  setNetInfo(state);
85
  });
86
 
87
+ // Also get initial state
88
+ Network.getNetworkStateAsync().then(setNetInfo).catch(() => {});
89
+
90
  // Load cellular setting
91
  const loadSetting = async () => {
92
  try {
 
104
  });
105
 
106
  return () => {
107
+ subscriptionNet.remove();
108
  subscription.remove();
109
  };
110
  }, []);
 
196
  return;
197
  }
198
 
199
+ if (netInfo?.type === Network.NetworkStateType.CELLULAR && !allowCellular) {
200
  Alert.alert('Waiting for Wi-Fi', 'Syncing on mobile data is disabled in Settings.');
201
  return;
202
  }
 
224
  try {
225
  const info = await MediaLibrary.getAssetInfoAsync(asset);
226
  const localUri = info.localUri || asset.uri;
227
+ const resp = await FileSystem.uploadAsync(`${BACKEND_URL}/upload?token=${MOCK_TOKEN}`, localUri, {
228
  fieldName: 'file',
229
  httpMethod: 'POST',
230
  uploadType: FileSystem.FileSystemUploadType.MULTIPART,
231
  parameters: { creation_time: asset.creationTime.toString() },
232
+ headers: { authorization: `Bearer ${MOCK_TOKEN}` },
233
  });
234
  if (resp.status === 200) {
235
  synced = await addSyncedId(asset.id, synced);
 
321
  return { text: 'Offline', icon: 'cloud-off', color: '#d93025', spinning: false };
322
  }
323
 
324
+ if (netInfo.type === Network.NetworkStateType.CELLULAR && !allowCellular && itemsToBackup > 0) {
325
  return { text: 'Waiting for Wi-Fi', icon: 'cloud-sync', color: '#f29900', spinning: false };
326
  }
327
 
 
450
  <TouchableOpacity
451
  style={styles.syncIconContainer}
452
  onPress={syncAssets}
453
+ disabled={isSyncing || (!netInfo?.isConnected) || (netInfo?.type === Network.NetworkStateType.CELLULAR && !allowCellular) || itemsToBackup === 0}
454
  >
455
  {statusUI.spinning ? (
456
  <ActivityIndicator size="small" color={statusUI.color} />
keystone-app/package-lock.json CHANGED
@@ -9,14 +9,14 @@
9
  "version": "1.0.0",
10
  "dependencies": {
11
  "@expo/metro-runtime": "~6.1.2",
12
- "@expo/vector-icons": "^15.1.1",
13
  "@react-native-async-storage/async-storage": "2.2.0",
14
- "@react-native-community/netinfo": "11.4.1",
15
  "date-fns": "^4.4.0",
16
  "expo": "^54.0.36",
17
  "expo-av": "~16.0.8",
18
  "expo-dev-client": "~6.0.21",
19
  "expo-file-system": "~19.0.23",
 
20
  "expo-image": "~3.0.11",
21
  "expo-linear-gradient": "~15.0.8",
22
  "expo-media-library": "~18.2.1",
@@ -2762,15 +2762,6 @@
2762
  "react-native": "^0.0.0-0 || >=0.65 <1.0"
2763
  }
2764
  },
2765
- "node_modules/@react-native-community/netinfo": {
2766
- "version": "11.4.1",
2767
- "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.4.1.tgz",
2768
- "integrity": "sha512-B0BYAkghz3Q2V09BF88RA601XursIEA111tnc2JOaN7axJWmNefmfjZqw/KdSxKZp7CZUuPpjBmz/WCR9uaHYg==",
2769
- "license": "MIT",
2770
- "peerDependencies": {
2771
- "react-native": ">=0.59"
2772
- }
2773
- },
2774
  "node_modules/@react-native/assets-registry": {
2775
  "version": "0.81.5",
2776
  "license": "MIT",
@@ -4587,11 +4578,10 @@
4587
  }
4588
  },
4589
  "node_modules/expo-font": {
4590
- "version": "57.0.1",
4591
- "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-57.0.1.tgz",
4592
- "integrity": "sha512-QyS9L1Kh9sKJg4gfU6rdbpxpmH+DyzBX8z6jVvXMUDoqLr1GqmkO/Wu379KCXjL///kWbhpNlbi7AgBuj4VdIQ==",
4593
  "license": "MIT",
4594
- "peer": true,
4595
  "dependencies": {
4596
  "fontfaceobserver": "^2.1.0"
4597
  },
@@ -5358,18 +5348,6 @@
5358
  "react-native": "*"
5359
  }
5360
  },
5361
- "node_modules/expo/node_modules/expo-font": {
5362
- "version": "14.0.12",
5363
- "license": "MIT",
5364
- "dependencies": {
5365
- "fontfaceobserver": "^2.1.0"
5366
- },
5367
- "peerDependencies": {
5368
- "expo": "*",
5369
- "react": "*",
5370
- "react-native": "*"
5371
- }
5372
- },
5373
  "node_modules/expo/node_modules/expo-keep-awake": {
5374
  "version": "15.0.8",
5375
  "license": "MIT",
 
9
  "version": "1.0.0",
10
  "dependencies": {
11
  "@expo/metro-runtime": "~6.1.2",
12
+ "@expo/vector-icons": "^15.0.3",
13
  "@react-native-async-storage/async-storage": "2.2.0",
 
14
  "date-fns": "^4.4.0",
15
  "expo": "^54.0.36",
16
  "expo-av": "~16.0.8",
17
  "expo-dev-client": "~6.0.21",
18
  "expo-file-system": "~19.0.23",
19
+ "expo-font": "~14.0.12",
20
  "expo-image": "~3.0.11",
21
  "expo-linear-gradient": "~15.0.8",
22
  "expo-media-library": "~18.2.1",
 
2762
  "react-native": "^0.0.0-0 || >=0.65 <1.0"
2763
  }
2764
  },
 
 
 
 
 
 
 
 
 
2765
  "node_modules/@react-native/assets-registry": {
2766
  "version": "0.81.5",
2767
  "license": "MIT",
 
4578
  }
4579
  },
4580
  "node_modules/expo-font": {
4581
+ "version": "14.0.12",
4582
+ "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.12.tgz",
4583
+ "integrity": "sha512-QQzunE2Mxk45AsCWm3tK7OpVljbtVnKD58q4/qliev+cbye1IOduUnRIdD+P7DyButw17G9MTX795kgaQiz5hQ==",
4584
  "license": "MIT",
 
4585
  "dependencies": {
4586
  "fontfaceobserver": "^2.1.0"
4587
  },
 
5348
  "react-native": "*"
5349
  }
5350
  },
 
 
 
 
 
 
 
 
 
 
 
 
5351
  "node_modules/expo/node_modules/expo-keep-awake": {
5352
  "version": "15.0.8",
5353
  "license": "MIT",
keystone-app/package.json CHANGED
@@ -4,14 +4,14 @@
4
  "main": "expo-router/entry",
5
  "dependencies": {
6
  "@expo/metro-runtime": "~6.1.2",
7
- "@expo/vector-icons": "^15.1.1",
8
  "@react-native-async-storage/async-storage": "2.2.0",
9
- "@react-native-community/netinfo": "11.4.1",
10
  "date-fns": "^4.4.0",
11
  "expo": "^54.0.36",
12
  "expo-av": "~16.0.8",
13
  "expo-dev-client": "~6.0.21",
14
  "expo-file-system": "~19.0.23",
 
15
  "expo-image": "~3.0.11",
16
  "expo-linear-gradient": "~15.0.8",
17
  "expo-media-library": "~18.2.1",
 
4
  "main": "expo-router/entry",
5
  "dependencies": {
6
  "@expo/metro-runtime": "~6.1.2",
7
+ "@expo/vector-icons": "^15.0.3",
8
  "@react-native-async-storage/async-storage": "2.2.0",
 
9
  "date-fns": "^4.4.0",
10
  "expo": "^54.0.36",
11
  "expo-av": "~16.0.8",
12
  "expo-dev-client": "~6.0.21",
13
  "expo-file-system": "~19.0.23",
14
+ "expo-font": "~14.0.12",
15
  "expo-image": "~3.0.11",
16
  "expo-linear-gradient": "~15.0.8",
17
  "expo-media-library": "~18.2.1",
keystone-backend/main.py CHANGED
@@ -1,6 +1,6 @@
1
  import os
2
  from datetime import datetime
3
- from fastapi import FastAPI, HTTPException, Header, Depends, UploadFile, File, Form
4
  from fastapi.middleware.cors import CORSMiddleware
5
  from pydantic import BaseModel
6
  from dotenv import load_dotenv
@@ -59,11 +59,15 @@ class LoginPayload(BaseModel):
59
  username: str
60
  password: str
61
 
62
- def verify_mock_token(authorization: str = Header(None)):
63
- if not authorization or not authorization.startswith("Bearer "):
64
- raise HTTPException(status_code=401, detail="Missing or invalid token format")
65
-
66
- token = authorization.split(" ")[1]
 
 
 
 
67
  if token != MOCK_TOKEN:
68
  raise HTTPException(status_code=401, detail="Unauthorized: Invalid Token")
69
  return token
 
1
  import os
2
  from datetime import datetime
3
+ from fastapi import FastAPI, HTTPException, Header, Depends, UploadFile, File, Form, Request
4
  from fastapi.middleware.cors import CORSMiddleware
5
  from pydantic import BaseModel
6
  from dotenv import load_dotenv
 
59
  username: str
60
  password: str
61
 
62
+ def verify_mock_token(request: Request, authorization: str = Header(None)):
63
+ token = None
64
+ if authorization and authorization.startswith("Bearer "):
65
+ token = authorization.split(" ")[1]
66
+
67
+ # Fallback to query parameter (useful if proxy strips Authorization header)
68
+ if not token:
69
+ token = request.query_params.get("token")
70
+
71
  if token != MOCK_TOKEN:
72
  raise HTTPException(status_code=401, detail="Unauthorized: Invalid Token")
73
  return token