OHO-7(Entire Fetching Emails)
This commit is contained in:
parent
0558ae9fd7
commit
185cd14252
3 changed files with 304 additions and 33 deletions
|
|
@ -7,6 +7,7 @@ const passport = require('passport');
|
||||||
const session = require('express-session');
|
const session = require('express-session');
|
||||||
const GoogleStrategy = require('passport-google-oauth20').Strategy;
|
const GoogleStrategy = require('passport-google-oauth20').Strategy;
|
||||||
const { OAuth2Client } = require('google-auth-library');
|
const { OAuth2Client } = require('google-auth-library');
|
||||||
|
const { google } = require('googleapis');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
@ -203,6 +204,66 @@ app.get('/api/transactions/recent', async (req, res) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Bank rules (can expand later)
|
||||||
|
const bankRules = [
|
||||||
|
{ name: 'HDFC Bank', email: 'alerts@hdfcbank.net' },
|
||||||
|
// { name: 'ICICI Bank', email: 'alerts@icicibank.com' }, // example expansion
|
||||||
|
];
|
||||||
|
|
||||||
|
// ✅ Gmail fetch function
|
||||||
|
async function fetchBankEmails(accessToken, bankEmails) {
|
||||||
|
const oauth2Client = new google.auth.OAuth2();
|
||||||
|
oauth2Client.setCredentials({ access_token: accessToken });
|
||||||
|
|
||||||
|
const gmail = google.gmail({ version: 'v1', auth: oauth2Client });
|
||||||
|
|
||||||
|
const allEmails = [];
|
||||||
|
|
||||||
|
for (const bankEmail of bankEmails) {
|
||||||
|
const res = await gmail.users.messages.list({
|
||||||
|
userId: 'me',
|
||||||
|
q: `from:${bankEmail}`,
|
||||||
|
maxResults: 20, // can adjust
|
||||||
|
});
|
||||||
|
|
||||||
|
const messages = res.data.messages || [];
|
||||||
|
for (const msg of messages) {
|
||||||
|
const fullMessage = await gmail.users.messages.get({
|
||||||
|
userId: 'me',
|
||||||
|
id: msg.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const snippet = fullMessage.data.snippet || "";
|
||||||
|
allEmails.push({
|
||||||
|
from: bankEmail,
|
||||||
|
snippet,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allEmails;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Gmail sync route
|
||||||
|
app.post('/api/sync-gmail', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { accessToken } = req.body;
|
||||||
|
|
||||||
|
if (!accessToken) {
|
||||||
|
return res.status(400).json({ error: "Missing access token" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const bankEmails = bankRules.map(b => b.email);
|
||||||
|
const emails = await fetchBankEmails(accessToken, bankEmails);
|
||||||
|
|
||||||
|
res.json({ success: true, emails });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Gmail fetch error:', error);
|
||||||
|
res.status(500).json({ error: 'Failed to fetch Gmail messages' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
/* ---------- SERVER ---------- */
|
/* ---------- SERVER ---------- */
|
||||||
mongoose.connect(process.env.MONGO_URI, {
|
mongoose.connect(process.env.MONGO_URI, {
|
||||||
useNewUrlParser: true,
|
useNewUrlParser: true,
|
||||||
|
|
|
||||||
89
Frontend/lib/email.dart
Normal file
89
Frontend/lib/email.dart
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class EmailsScreen extends StatefulWidget {
|
||||||
|
final String accessToken;
|
||||||
|
final String userId;
|
||||||
|
|
||||||
|
const EmailsScreen({
|
||||||
|
super.key,
|
||||||
|
required this.accessToken,
|
||||||
|
required this.userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<EmailsScreen> createState() => _EmailsScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EmailsScreenState extends State<EmailsScreen> {
|
||||||
|
List<dynamic> _emails = [];
|
||||||
|
bool _loading = true;
|
||||||
|
String? _error;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
fetchEmails();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> fetchEmails() async {
|
||||||
|
try {
|
||||||
|
final uri = Uri.parse("http://192.168.1.10:3000/api/sync-gmail");
|
||||||
|
|
||||||
|
final res = await http.post(
|
||||||
|
uri,
|
||||||
|
headers: {"Content-Type": "application/json"},
|
||||||
|
body: jsonEncode({"accessToken": widget.accessToken}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.statusCode == 200) {
|
||||||
|
final data = jsonDecode(res.body);
|
||||||
|
setState(() {
|
||||||
|
_emails = data['emails'] ?? [];
|
||||||
|
_loading = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_error = "Backend error: ${res.body}";
|
||||||
|
_loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setState(() {
|
||||||
|
_error = "⚠ Error fetching emails: $e";
|
||||||
|
_loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text("Bank Emails"),
|
||||||
|
actions: [
|
||||||
|
IconButton(icon: const Icon(Icons.refresh), onPressed: fetchEmails),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body:
|
||||||
|
_loading
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: _error != null
|
||||||
|
? Center(child: Text(_error!))
|
||||||
|
: _emails.isEmpty
|
||||||
|
? const Center(child: Text("No bank-related emails found."))
|
||||||
|
: ListView.builder(
|
||||||
|
itemCount: _emails.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final email = _emails[index];
|
||||||
|
return ListTile(
|
||||||
|
title: Text(email['subject'] ?? "No subject"),
|
||||||
|
subtitle: Text(email['snippet'] ?? ""),
|
||||||
|
leading: const Icon(Icons.email),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,124 @@
|
||||||
|
// // ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
|
// import 'package:flutter/material.dart';
|
||||||
|
// import 'package:google_sign_in/google_sign_in.dart';
|
||||||
|
// import 'package:http/http.dart' as http;
|
||||||
|
// import 'package:monarch/email.dart';
|
||||||
|
// import 'package:monarch/other_pages/enviroment.dart';
|
||||||
|
// import 'dart:convert';
|
||||||
|
// import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
// import 'package:monarch/main_pages/HomePage/homepage.dart';
|
||||||
|
|
||||||
|
// class LoginScreen extends StatefulWidget {
|
||||||
|
// const LoginScreen({super.key});
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// State<LoginScreen> createState() => _LoginScreenState();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// class _LoginScreenState extends State<LoginScreen> {
|
||||||
|
// // GoogleSignIn instance
|
||||||
|
// final GoogleSignIn _googleSignIn = GoogleSignIn(
|
||||||
|
// scopes: ['email', 'profile', 'openid'],
|
||||||
|
// serverClientId: '${Environment.serverClientId}',
|
||||||
|
// );
|
||||||
|
|
||||||
|
// bool _isLoading = false;
|
||||||
|
|
||||||
|
// Future<void> _handleGoogleSignIn() async {
|
||||||
|
// setState(() => _isLoading = true);
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// // Step 1: Trigger the sign-in flow
|
||||||
|
// final GoogleSignInAccount? account = await _googleSignIn.signIn();
|
||||||
|
|
||||||
|
// if (account == null) {
|
||||||
|
// // User cancelled
|
||||||
|
// setState(() => _isLoading = false);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Step 2: Get authentication tokens
|
||||||
|
// final GoogleSignInAuthentication auth = await account.authentication;
|
||||||
|
// final String? idToken = auth.idToken;
|
||||||
|
|
||||||
|
// if (idToken == null) {
|
||||||
|
// debugPrint("❌ Failed to get ID token");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Step 3: Send token to backend
|
||||||
|
// final uri = Uri.parse("http://192.168.1.10:3000/auth/google/token");
|
||||||
|
// final res = await http.post(
|
||||||
|
// uri,
|
||||||
|
// headers: {"Content-Type": "application/json"},
|
||||||
|
// body: jsonEncode({"idToken": idToken}),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (res.statusCode == 200) {
|
||||||
|
// final data = jsonDecode(res.body);
|
||||||
|
// if (data['success'] == true) {
|
||||||
|
// debugPrint("✅ User logged in: ${data['user']}");
|
||||||
|
// final prefs = await SharedPreferences.getInstance();
|
||||||
|
// prefs.setString('userId', data['user']['_id']);
|
||||||
|
// Navigator.pushReplacement(
|
||||||
|
// context,
|
||||||
|
// MaterialPageRoute(builder: (_) => const EmailsScreen(accessToken: '', userId: 'userId',)),
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// debugPrint("❌ Login failed: ${data['error']}");
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// debugPrint("❌ Backend error: ${res.body}");
|
||||||
|
// }
|
||||||
|
// } catch (e) {
|
||||||
|
// debugPrint("⚠ Google Sign-In error: $e");
|
||||||
|
// } finally {
|
||||||
|
// setState(() => _isLoading = false);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Widget build(BuildContext context) {
|
||||||
|
// return Scaffold(
|
||||||
|
// backgroundColor: Colors.white,
|
||||||
|
// body: Center(
|
||||||
|
// child:
|
||||||
|
// _isLoading
|
||||||
|
// ? const CircularProgressIndicator()
|
||||||
|
// : ElevatedButton.icon(
|
||||||
|
// style: ElevatedButton.styleFrom(
|
||||||
|
// backgroundColor: Colors.white,
|
||||||
|
// foregroundColor: Colors.black,
|
||||||
|
// padding: const EdgeInsets.symmetric(
|
||||||
|
// vertical: 12,
|
||||||
|
// horizontal: 20,
|
||||||
|
// ),
|
||||||
|
// shape: RoundedRectangleBorder(
|
||||||
|
// borderRadius: BorderRadius.circular(30),
|
||||||
|
// side: const BorderSide(color: Colors.grey),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
|
||||||
|
// label: const Text(
|
||||||
|
// 'Sign in with Google',
|
||||||
|
// style: TextStyle(fontSize: 16),
|
||||||
|
// ),
|
||||||
|
// onPressed: _handleGoogleSignIn,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_sign_in/google_sign_in.dart';
|
import 'package:google_sign_in/google_sign_in.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:monarch/email.dart';
|
||||||
import 'package:monarch/other_pages/enviroment.dart';
|
import 'package:monarch/other_pages/enviroment.dart';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:monarch/main_pages/HomePage/homepage.dart';
|
|
||||||
|
|
||||||
class LoginScreen extends StatefulWidget {
|
class LoginScreen extends StatefulWidget {
|
||||||
const LoginScreen({super.key});
|
const LoginScreen({super.key});
|
||||||
|
|
@ -16,10 +128,14 @@ class LoginScreen extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LoginScreenState extends State<LoginScreen> {
|
class _LoginScreenState extends State<LoginScreen> {
|
||||||
// GoogleSignIn instance
|
|
||||||
final GoogleSignIn _googleSignIn = GoogleSignIn(
|
final GoogleSignIn _googleSignIn = GoogleSignIn(
|
||||||
scopes: ['email', 'profile', 'openid'],
|
scopes: [
|
||||||
serverClientId: '${Environment.serverClientId}',
|
'email',
|
||||||
|
'profile',
|
||||||
|
'openid',
|
||||||
|
'https://www.googleapis.com/auth/gmail.readonly'
|
||||||
|
],
|
||||||
|
serverClientId: Environment.serverClientId,
|
||||||
);
|
);
|
||||||
|
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
|
|
@ -28,25 +144,24 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||||
setState(() => _isLoading = true);
|
setState(() => _isLoading = true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Step 1: Trigger the sign-in flow
|
|
||||||
final GoogleSignInAccount? account = await _googleSignIn.signIn();
|
final GoogleSignInAccount? account = await _googleSignIn.signIn();
|
||||||
|
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
// User cancelled
|
|
||||||
setState(() => _isLoading = false);
|
setState(() => _isLoading = false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Get authentication tokens
|
|
||||||
final GoogleSignInAuthentication auth = await account.authentication;
|
final GoogleSignInAuthentication auth = await account.authentication;
|
||||||
final String? idToken = auth.idToken;
|
|
||||||
|
|
||||||
if (idToken == null) {
|
final String? idToken = auth.idToken;
|
||||||
debugPrint("❌ Failed to get ID token");
|
final String? accessToken = auth.accessToken;
|
||||||
|
|
||||||
|
if (idToken == null || accessToken == null) {
|
||||||
|
debugPrint("❌ Failed to get tokens");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: Send token to backend
|
// Send idToken to backend for login
|
||||||
final uri = Uri.parse("http://192.168.1.10:3000/auth/google/token");
|
final uri = Uri.parse("http://192.168.1.10:3000/auth/google/token");
|
||||||
final res = await http.post(
|
final res = await http.post(
|
||||||
uri,
|
uri,
|
||||||
|
|
@ -57,12 +172,19 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
final data = jsonDecode(res.body);
|
final data = jsonDecode(res.body);
|
||||||
if (data['success'] == true) {
|
if (data['success'] == true) {
|
||||||
debugPrint("✅ User logged in: ${data['user']}");
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
prefs.setString('userId', data['user']['_id']);
|
prefs.setString('userId', data['user']['_id']);
|
||||||
|
prefs.setString('accessToken', accessToken);
|
||||||
|
|
||||||
|
// Navigate directly to EmailsScreen with accessToken
|
||||||
Navigator.pushReplacement(
|
Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (_) => const FinTrackHomePage()),
|
MaterialPageRoute(
|
||||||
|
builder: (_) => EmailsScreen(
|
||||||
|
accessToken: accessToken,
|
||||||
|
userId: data['user']['_id'],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
debugPrint("❌ Login failed: ${data['error']}");
|
debugPrint("❌ Login failed: ${data['error']}");
|
||||||
|
|
@ -82,8 +204,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
body: Center(
|
body: Center(
|
||||||
child:
|
child: _isLoading
|
||||||
_isLoading
|
|
||||||
? const CircularProgressIndicator()
|
? const CircularProgressIndicator()
|
||||||
: ElevatedButton.icon(
|
: ElevatedButton.icon(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
|
|
@ -98,7 +219,6 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||||
side: const BorderSide(color: Colors.grey),
|
side: const BorderSide(color: Colors.grey),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
label: const Text(
|
label: const Text(
|
||||||
'Sign in with Google',
|
'Sign in with Google',
|
||||||
style: TextStyle(fontSize: 16),
|
style: TextStyle(fontSize: 16),
|
||||||
|
|
@ -109,3 +229,4 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue