diff --git a/Backend/index.js b/Backend/index.js index 1c9d6fd..fc46bd6 100644 --- a/Backend/index.js +++ b/Backend/index.js @@ -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, diff --git a/Frontend/lib/email.dart b/Frontend/lib/email.dart new file mode 100644 index 0000000..2fae83e --- /dev/null +++ b/Frontend/lib/email.dart @@ -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 createState() => _EmailsScreenState(); +} + +class _EmailsScreenState extends State { + List _emails = []; + bool _loading = true; + String? _error; + + @override + void initState() { + super.initState(); + fetchEmails(); + } + + Future 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), + ); + }, + ), + ); + } +} diff --git a/Frontend/lib/login.dart b/Frontend/lib/login.dart index 669686e..4bb814b 100644 --- a/Frontend/lib/login.dart +++ b/Frontend/lib/login.dart @@ -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 createState() => _LoginScreenState(); +// } + +// class _LoginScreenState extends State { +// // GoogleSignIn instance +// final GoogleSignIn _googleSignIn = GoogleSignIn( +// scopes: ['email', 'profile', 'openid'], +// serverClientId: '${Environment.serverClientId}', +// ); + +// bool _isLoading = false; + +// Future _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 { - // 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 { 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 { 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 { 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, + ), ), ); } } +