diff --git a/.gitignore b/.gitignore index 807f36a..56d2180 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ src/extension/lib/* extension.crx extension.pem .DS_Store -extension.zip \ No newline at end of file +extension.zip +client_secret_331738423824-bt52qqf8k1aaa5icjfkp0fu1ebo29e07.apps.googleusercontent.com.json \ No newline at end of file diff --git a/how-to-use.html b/how-to-use.html new file mode 100644 index 0000000..39058f6 --- /dev/null +++ b/how-to-use.html @@ -0,0 +1,69 @@ + + +
+ + +Follow these simple steps to generate your perfect cover letter
+Add Coverquai to Chrome from the Chrome Web Store. It's completely free!
+Upload your resume once and set it as default for quick access.
+Navigate to any job posting on supported websites.
+Currently supported websites: LinkedIn, Glassdoor, Workday, Eightfold AI
+Click the Coverquai icon and watch as AI creates your personalized cover letter.
+Download the cover letter as a PDF or copy the text directly.
+Generate tailored cover letters instantly using AI
diff --git a/screenshots/glassdoor_generation.png b/screenshots/glassdoor_generation.png new file mode 100644 index 0000000..bc9b32c Binary files /dev/null and b/screenshots/glassdoor_generation.png differ diff --git a/screenshots/linkedin_generation.png b/screenshots/linkedin_generation.png new file mode 100644 index 0000000..2d5b198 Binary files /dev/null and b/screenshots/linkedin_generation.png differ diff --git a/screenshots/options_page.png b/screenshots/options_page.png new file mode 100644 index 0000000..2817e51 Binary files /dev/null and b/screenshots/options_page.png differ diff --git a/screenshots/step1.png b/screenshots/step1.png new file mode 100644 index 0000000..483e82c Binary files /dev/null and b/screenshots/step1.png differ diff --git a/screenshots/step2.png b/screenshots/step2.png new file mode 100644 index 0000000..fdacdf8 Binary files /dev/null and b/screenshots/step2.png differ diff --git a/screenshots/step4.png b/screenshots/step4.png new file mode 100644 index 0000000..2bf638a Binary files /dev/null and b/screenshots/step4.png differ diff --git a/src/backend/main.py b/src/backend/main.py index 521d9e6..3d9142c 100644 --- a/src/backend/main.py +++ b/src/backend/main.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI, UploadFile, HTTPException, Depends, Header +from fastapi import FastAPI, UploadFile, HTTPException, Depends, Header, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import StreamingResponse import PyPDF2 @@ -11,7 +11,7 @@ import logging import uuid from supabase import create_client, Client -from datetime import datetime +from datetime import datetime, date app = FastAPI() @@ -29,10 +29,27 @@ # Configure CORS app.add_middleware( CORSMiddleware, - allow_origins=["chrome-extension://*","http://localhost:8000"], + allow_origins=[ + "chrome-extension://*", + "http://localhost:8000", + "https://accounts.google.com", + "https://*.googleusercontent.com", + "https://oauth2.googleapis.com", + ], allow_credentials=True, allow_methods=["*"], - allow_headers=["*"], + allow_headers=[ + "Authorization", + "Content-Type", + "X-Requested-With", + "Accept", + "Origin", + "Access-Control-Request-Method", + "Access-Control-Request-Headers", + "Access-Control-Allow-Origin", + ], + expose_headers=["*"], + max_age=600, # Cache preflight requests for 10 minutes ) # Configure logging @@ -74,6 +91,10 @@ class SignInRequest(BaseModel): class RefreshTokenRequest(BaseModel): refresh_token: str +# Add new auth model +class GoogleAuthRequest(BaseModel): + credential: str # This will be the ID token from Google + # Auth dependency async def verify_token(authorization: Annotated[str | None, Header()] = None): if not authorization or not authorization.startswith("Bearer "): @@ -118,6 +139,19 @@ async def logout(user = Depends(verify_token)): except Exception as e: raise HTTPException(status_code=400, detail=str(e)) +@app.post("/auth/google") +async def google_auth(request: GoogleAuthRequest): + try: + print(request.credential) + # Verify the Google token and sign in/up with Supabase + response = supabase.auth.sign_in_with_id_token({ + "provider": "google", + "token": request.credential + }) + print(response.dict()) + return response.dict() + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) @app.post("/upload-resume") async def upload_resume(file: UploadFile,user = Depends(verify_token)): @@ -314,4 +348,24 @@ async def refresh_token(request: RefreshTokenRequest): } except Exception as e: logger.error(f"Token refresh failed: {str(e)}") - raise HTTPException(status_code=401, detail="Invalid refresh token") \ No newline at end of file + raise HTTPException(status_code=401, detail="Invalid refresh token") + +@app.get("/countgenerations") +async def count_generations(user = Depends(verify_token)): + try: + # Get today's date in UTC + today = datetime.now().strftime('%Y-%m-%d') + + # Query Supabase using proper timestamp comparison + response = supabase.table('cover_letter_requests')\ + .select('id')\ + .eq('userid', user.user.id)\ + .gte('created_at', f'{today}T00:00:00')\ + .lt('created_at', f'{today}T23:59:59')\ + .execute() + + count = len(response.data) + return {"count": count} + except Exception as e: + logger.error(f"Failed to count generations: {str(e)}") + raise HTTPException(status_code=500, detail=str(e)) \ No newline at end of file diff --git a/src/extension/background.js b/src/extension/background.js index 115a79a..ad44f4f 100644 --- a/src/extension/background.js +++ b/src/extension/background.js @@ -6,6 +6,7 @@ Paragraph 2: Summarize your experience. Paragraph 3: How your experience translates to the job. Paragraph 4: Closing and contact information.`; +const API_URL = 'https://cvwriter-bhargavyagniks-projects.vercel.app'; chrome.runtime.onInstalled.addListener(() => { console.log('Coverquai Extension installed'); @@ -21,6 +22,12 @@ chrome.runtime.onInstalled.addListener(() => { .catch(error => sendResponse({ success: false, error: error.message })); return true; // Required for async response } + if (request.action === 'googleLogin') { + handleGoogleLogin() + .then(data => sendResponse({ success: true, data })) + .catch(error => sendResponse({ success: false, error: error.message })); + return true; // Required to use sendResponse asynchronously + } }); chrome.runtime.onConnect.addListener((port) => { if (port.name === 'coverLetterStream') { @@ -45,7 +52,7 @@ chrome.runtime.onInstalled.addListener(() => { systemPrompt: DEFAULT_SYSTEM_PROMPT }); - const response = await fetch('https://cvwriter-git-dev-bhargavyagniks-projects.vercel.app/generate-cover-letter', { + const response = await fetch(`${API_URL}/generate-cover-letter`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -117,4 +124,77 @@ chrome.runtime.onInstalled.addListener(() => { } finally { port.postMessage({ type: 'done' }); } +} + +async function handleGoogleLogin() { + const authParams = new URLSearchParams({ + client_id: '331738423824-jqcka65qqq8bp0c6dmi4q662s4f027v5.apps.googleusercontent.com', + response_type: 'id_token', + access_type: 'offline', + redirect_uri: chrome.identity.getRedirectURL(), + scope: 'openid email profile', + prompt: 'consent' + }); + + const authUrl = `https://accounts.google.com/o/oauth2/auth?${authParams.toString()}`; + + try { + const responseUrl = await new Promise((resolve, reject) => { + chrome.identity.launchWebAuthFlow({ + url: authUrl, + interactive: true + }, (response) => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError); + } else { + resolve(response); + } + }); + }); + + const hashFragment = responseUrl.split('#')[1]; + if (!hashFragment) { + throw new Error('No token received from Google'); + } + + const urlParams = new URLSearchParams(hashFragment); + const googleToken = urlParams.get('id_token'); + if (!googleToken) { + throw new Error('No ID token found in response'); + } + + const response = await fetch(`${API_URL}/auth/google`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ credential: googleToken }) + }); + + const data = await response.json(); + if (!response.ok) { + throw new Error(data.detail || 'Google login failed'); + } + // Store auth data + await chrome.storage.local.set({ + 'authToken': data.session.access_token, + 'refreshToken': data.session.refresh_token, + 'user': data.user.id + }); + + // Reopen the extension popup + const extensionId = chrome.runtime.id; + await chrome.action.openPopup(); + + // Return the data to the original request + return data; + } catch (error) { + console.error('Google login error:', error); + throw error; + } +} + +// Helper function to get the popup URL +function getPopupUrl() { + return chrome.runtime.getURL('popup.html'); } \ No newline at end of file diff --git a/src/extension/content.js b/src/extension/content.js index 233dc49..f80d6e4 100644 --- a/src/extension/content.js +++ b/src/extension/content.js @@ -11,17 +11,53 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { return true; // Required for async response }); + function scrapeCurrentPage() { if (window.location.href.includes('linkedin.com')) { + console.log("linkedin.com"); const description = document.querySelector('#job-details > div > p')?.textContent || ''; + console.log(description); return { title: "-",//title.trim(), company: "-",//company.trim(), description: description,//description.trim(), }; } - + if (window.location.href.includes('eightfold.ai')) { + console.log("eightfold.ai"); + const title = document.querySelector('#pcs-body-container > div:nth-child(2) > div.search-results-main-container > div > div.inline-block.mobile-hide.position-top-container > div > div > div:nth-child(2) > div > h1')?.textContent || ''; + const description = document.querySelector('#pcs-body-container > div:nth-child(2) > div.search-results-main-container > div > div.inline-block.mobile-hide.position-top-container > div > div > div.position-details > div.row > div > div > div:nth-child(2) > div > div').textContent || ''; + console.log(title, description, company); + return { + title: title.trim(), + company: window.location.hostname.split('.')[0], + description: description.trim(), + }; + } + if (window.location.href.includes('glassdoor.com') || window.location.href.includes('glassdoor.ca')) { + console.log("glassdoor.com"); + const company = document.evaluate('/html/body/div[3]/div[2]/div[4]/div/div[2]/div/div[1]/header/div[1]/a/div[2]/div[1]/h4',document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue?.textContent || ''; + const title = document.evaluate('/html/body/div[3]/div[2]/div[4]/div/div[2]/div/div[1]/header/div[1]/h1',document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue?.textContent || ''; + const description = document.evaluate('/html/body/div[3]/div[2]/div[4]/div/div[2]/div/div[1]/section/div[2]/div[1]',document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue?.textContent || ''; + console.log(title, description, company); + return { + title: title.trim(), + company: company.trim(), + description: description.trim(), + }; + } + if (window.location.href.includes('myworkdayjobs.com')) { + const title = document.querySelector('[data-automation-id="jobPostingHeader"]')?.textContent || ''; + const description = document.querySelector('[data-automation-id="jobPostingDescription"]')?.textContent || ''; + const company = window.location.hostname.split('.')[0] || ''; // Gets 'fil' from fil.wd3.myworkdayjobs.com + console.log("Workday scraping:", { title, description, company }); + return { + title: title.trim(), + company: company.trim(), + description: description.trim(), + }; + } return { title: "Job",//title.trim(), company: "Company",//company.trim(), diff --git a/src/extension/login.html b/src/extension/login.html index 8bb8acc..75ddca1 100644 --- a/src/extension/login.html +++ b/src/extension/login.html @@ -6,10 +6,10 @@By using Coverquai, you agree to these Terms of Service. If you do not agree, please do not use the extension.
+ +Coverquai is a browser extension that generates cover letters using AI technology. While we strive for quality, we cannot guarantee the accuracy or suitability of generated content.
+ +We handle your data according to our Privacy Policy. By using Coverquai, you consent to the collection and use of information as described therein.
+ +You retain rights to your uploaded content. Generated cover letters are for your personal use only.
+ +Coverquai is provided "as is" without warranties of any kind. We are not liable for any damages arising from your use of the service.
+ +We reserve the right to modify these terms at any time. Continued use of Coverquai constitutes acceptance of modified terms.
+ +For questions about these terms, please contact us at coverquai@bhargavyagnik.com
+ +