refactor app configuration and improve Google OAuth handling; update .gitignore and enhance test documentation

This commit is contained in:
bobbert committed 2026-02-22 22:35:57 +00:00
1 parent cb74405f5f
commit d865708e28
6 files changed
+79 -41

No files matched your search

+2
View File
@@ -1,2 +1,4 @@
!/Ignore files/ !/Ignore files/
!/package-lock.json !/package-lock.json
!/.expo/
!/.idea/
+20 -19
View File
@@ -1,22 +1,23 @@
/** // Google Photos API Test
* Google Photos OAuth & API Test //
* // Usage:
* Usage: // node TEST/testGooglePhotosAPI.js <ACCESS_TOKEN>
* node TEST/testGooglePhotosAPI.js <ACCESS_TOKEN> // GOOGLE_ACCESS_TOKEN=ya29.xxx node TEST/testGooglePhotosAPI.js
* GOOGLE_ACCESS_TOKEN=ya29.xxx node TEST/testGooglePhotosAPI.js //
* // Getting a token from OAuth Playground (https://developers.google.com/oauthplayground):
* How to get a token for manual testing: // 1. Click the gear icon → check "Use your own OAuth credentials"
* 1. Go to https://developers.google.com/oauthplayground // 2. Enter your Client ID and Client Secret from Google Cloud Console
* 2. Select "Photos Library API v1" → photoslibrary scope // 3. In the left panel select ALL of these scopes:
* 3. Authorise the API // https://www.googleapis.com/auth/photoslibrary
* 4. Exchange the authorization code for tokens // https://www.googleapis.com/auth/photoslibrary.appendonly
* 5. Copy the access token and pass it to this script // https://www.googleapis.com/auth/userinfo.email
* // https://www.googleapis.com/auth/userinfo.profile
* Tests: // 4. Click "Authorize APIs" and sign in with your test Google account
* 1. Validates the token against the userinfo endpoint // 5. Click "Exchange authorization code for tokens"
* 2. Lists media items from Google Photos // 6. Copy the Access token (starts with ya29.) and pass it here
* 3. Lists albums from Google Photos //
*/ // Note: if you only see 403 PERMISSION_DENIED, the token was issued without
// the photoslibrary scope — go back and re-authorise with all scopes above.
const API_BASE = 'https://photoslibrary.googleapis.com/v1'; const API_BASE = 'https://photoslibrary.googleapis.com/v1';
const USERINFO_URL = 'https://www.googleapis.com/oauth2/v2/userinfo'; const USERINFO_URL = 'https://www.googleapis.com/oauth2/v2/userinfo';
+12 -7
View File
@@ -1,12 +1,12 @@
{ {
"expo": { "expo": {
"name": "Media-Aggregation-and-Sorting-System", "name": "MASS Photo App",
"slug": "Media-Aggregation-and-Sorting-System", "slug": "mass-expo",
"owner": "bobthebob",
"version": "1.0.0", "version": "1.0.0",
"orientation": "portrait", "orientation": "portrait",
"icon": "./assets/icon.png", "icon": "./assets/icon.png",
"userInterfaceStyle": "light", "userInterfaceStyle": "automatic",
"newArchEnabled": true,
"splash": { "splash": {
"image": "./assets/splash-icon.png", "image": "./assets/splash-icon.png",
"resizeMode": "contain", "resizeMode": "contain",
@@ -16,11 +16,11 @@
"supportsTablet": true "supportsTablet": true
}, },
"android": { "android": {
"package": "com.bobthebob.massphotoapp",
"adaptiveIcon": { "adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png", "foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff" "backgroundColor": "#ffffff"
}, }
"edgeToEdgeEnabled": true
}, },
"web": { "web": {
"favicon": "./assets/favicon.png" "favicon": "./assets/favicon.png"
@@ -28,6 +28,11 @@
"scheme": "media-aggregation", "scheme": "media-aggregation",
"plugins": [ "plugins": [
"expo-sqlite" "expo-sqlite"
] ],
"extra": {
"eas": {
"projectId": "472bba14-1f7f-4cbe-b37b-3ec2c4077d03"
}
}
} }
} }
+30
View File
@@ -0,0 +1,30 @@
{
"cli": {
"version": ">= 16.0.0",
"appVersionSource": "local"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"android": {
"buildType": "apk"
}
},
"preview": {
"distribution": "internal",
"android": {
"buildType": "apk"
}
},
"production": {
"android": {
"buildType": "app-bundle"
}
}
},
"submit": {
"production": {}
}
}
+10 -10
View File
@@ -4,16 +4,20 @@ import { AuthServiceBase } from './AuthServiceBase';
WebBrowser.maybeCompleteAuthSession(); WebBrowser.maybeCompleteAuthSession();
// Replace with your real Google Cloud OAuth Client ID (see ignorefiles/GOOGLE_OAUTH_SETUP.md)
const GOOGLE_CLIENT_ID = '313090284964-rgq1u7np6ogucu9o97s134n5nc6nj7kf.apps.googleusercontent.com'; const GOOGLE_CLIENT_ID = '313090284964-rgq1u7np6ogucu9o97s134n5nc6nj7kf.apps.googleusercontent.com';
// makeRedirectUri uses the scheme from app.json ("media-aggregation") to build
// the deep link URI that Android will route back into the app after OAuth.
// The resulting URI is: media-aggregation:// (registered in Google Cloud Console)
// For Expo Go dev testing the loopback http://localhost is used instead.
const REDIRECT_URI = AuthSession.makeRedirectUri({ scheme: 'media-aggregation' });
const GOOGLE_DISCOVERY = { const GOOGLE_DISCOVERY = {
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth', authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenEndpoint: 'https://oauth2.googleapis.com/token', tokenEndpoint: 'https://oauth2.googleapis.com/token',
revocationEndpoint: 'https://oauth2.googleapis.com/revoke', revocationEndpoint: 'https://oauth2.googleapis.com/revoke',
}; };
// Google Photos Library API scopes — read, write, and user profile
const SCOPES = [ const SCOPES = [
'https://www.googleapis.com/auth/photoslibrary', 'https://www.googleapis.com/auth/photoslibrary',
'https://www.googleapis.com/auth/photoslibrary.appendonly', 'https://www.googleapis.com/auth/photoslibrary.appendonly',
@@ -48,15 +52,12 @@ class GoogleAUTH extends AuthServiceBase {
}; };
} }
const redirectUri = AuthSession.makeRedirectUri({ console.log('OAuth redirect URI:', REDIRECT_URI);
scheme: 'media-aggregation',
preferLocalhost: true,
});
const authRequest = new AuthSession.AuthRequest({ const authRequest = new AuthSession.AuthRequest({
clientId: GOOGLE_CLIENT_ID, clientId: GOOGLE_CLIENT_ID,
scopes: SCOPES, scopes: SCOPES,
redirectUri, redirectUri: REDIRECT_URI,
responseType: AuthSession.ResponseType.Code, responseType: AuthSession.ResponseType.Code,
usePKCE: true, usePKCE: true,
extraParams: { access_type: 'offline', prompt: 'consent' }, extraParams: { access_type: 'offline', prompt: 'consent' },
@@ -72,7 +73,7 @@ class GoogleAUTH extends AuthServiceBase {
{ {
clientId: GOOGLE_CLIENT_ID, clientId: GOOGLE_CLIENT_ID,
code: result.params.code, code: result.params.code,
redirectUri, redirectUri: REDIRECT_URI,
extraParams: { code_verifier: authRequest.codeVerifier }, extraParams: { code_verifier: authRequest.codeVerifier },
}, },
GOOGLE_DISCOVERY GOOGLE_DISCOVERY
@@ -80,7 +81,6 @@ class GoogleAUTH extends AuthServiceBase {
this.accessToken = tokenResponse.accessToken; this.accessToken = tokenResponse.accessToken;
this.refreshToken = tokenResponse.refreshToken; this.refreshToken = tokenResponse.refreshToken;
this.idToken = tokenResponse.idToken;
this.expiresAt = tokenResponse.issuedAt this.expiresAt = tokenResponse.issuedAt
? (tokenResponse.issuedAt + (tokenResponse.expiresIn || 3600)) * 1000 ? (tokenResponse.issuedAt + (tokenResponse.expiresIn || 3600)) * 1000
: Date.now() + 3600000; : Date.now() + 3600000;
@@ -91,7 +91,7 @@ class GoogleAUTH extends AuthServiceBase {
return { return {
success: true, success: true,
accessToken: this.accessToken, accessToken: this.accessToken,
refreshToken: this.refreshToken, refreshToken: null,
email: this.email, email: this.email,
}; };
} catch (error) { } catch (error) {
+5 -5
View File
@@ -199,13 +199,13 @@ const styles = StyleSheet.create({
centerContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 24 }, centerContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 24 },
loadingText: { marginTop: 12, fontSize: 14 }, loadingText: { marginTop: 12, fontSize: 14 },
// Filter chips // Filter chips
filterRow: { paddingHorizontal: 12, paddingVertical: 8, gap: 8 }, filterRow: { paddingHorizontal: 12, paddingVertical: 6, gap: 6 },
chip: { chip: {
flexDirection: 'row', alignItems: 'center', paddingHorizontal: 14, paddingVertical: 7, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 10, paddingVertical: 4,
borderRadius: 20, gap: 6, borderRadius: 12, gap: 4,
}, },
chipIcon: { fontSize: 13 }, chipIcon: { fontSize: 11 },
chipText: { fontSize: 13, fontWeight: '500', color: '#3c4043' }, chipText: { fontSize: 12, fontWeight: '500', color: '#3c4043' },
// Empty // Empty
emptyTitle: { fontSize: 18, fontWeight: '600', marginBottom: 6 }, emptyTitle: { fontSize: 18, fontWeight: '600', marginBottom: 6 },
emptyText: { fontSize: 13, textAlign: 'center', marginBottom: 20 }, emptyText: { fontSize: 13, textAlign: 'center', marginBottom: 20 },