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 GoogleStrategy = require('passport-google-oauth20').Strategy;
|
||||
const { OAuth2Client } = require('google-auth-library');
|
||||
const { google } = require('googleapis');
|
||||
|
||||
const app = express();
|
||||
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 ---------- */
|
||||
mongoose.connect(process.env.MONGO_URI, {
|
||||
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
|
||||
|
||||
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});
|
||||
|
|
@ -16,10 +128,14 @@ class LoginScreen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _LoginScreenState extends State<LoginScreen> {
|
||||
// GoogleSignIn instance
|
||||
final GoogleSignIn _googleSignIn = GoogleSignIn(
|
||||
scopes: ['email', 'profile', 'openid'],
|
||||
serverClientId: '${Environment.serverClientId}',
|
||||
scopes: [
|
||||
'email',
|
||||
'profile',
|
||||
'openid',
|
||||
'https://www.googleapis.com/auth/gmail.readonly'
|
||||
],
|
||||
serverClientId: Environment.serverClientId,
|
||||
);
|
||||
|
||||
bool _isLoading = false;
|
||||
|
|
@ -28,25 +144,24 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
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");
|
||||
final String? idToken = auth.idToken;
|
||||
final String? accessToken = auth.accessToken;
|
||||
|
||||
if (idToken == null || accessToken == null) {
|
||||
debugPrint("❌ Failed to get tokens");
|
||||
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 res = await http.post(
|
||||
uri,
|
||||
|
|
@ -57,12 +172,19 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
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']);
|
||||
prefs.setString('accessToken', accessToken);
|
||||
|
||||
// Navigate directly to EmailsScreen with accessToken
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const FinTrackHomePage()),
|
||||
MaterialPageRoute(
|
||||
builder: (_) => EmailsScreen(
|
||||
accessToken: accessToken,
|
||||
userId: data['user']['_id'],
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
debugPrint("❌ Login failed: ${data['error']}");
|
||||
|
|
@ -82,30 +204,29 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
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),
|
||||
),
|
||||
child: _isLoading
|
||||
? const CircularProgressIndicator()
|
||||
: ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
horizontal: 20,
|
||||
),
|
||||
|
||||
label: const Text(
|
||||
'Sign in with Google',
|
||||
style: TextStyle(fontSize: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
side: const BorderSide(color: Colors.grey),
|
||||
),
|
||||
onPressed: _handleGoogleSignIn,
|
||||
),
|
||||
label: const Text(
|
||||
'Sign in with Google',
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
onPressed: _handleGoogleSignIn,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue