Skip to content

Commit

Permalink
major updates, added Google auth, removed email signup, better UI, mo…
Browse files Browse the repository at this point in the history
…re easy workflow
  • Loading branch information
bhargavyagnik committed Nov 18, 2024
1 parent 384c3c3 commit 0d3b758
Show file tree
Hide file tree
Showing 23 changed files with 677 additions and 124 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ src/extension/lib/*
extension.crx
extension.pem
.DS_Store
extension.zip
extension.zip
client_secret_331738423824-bt52qqf8k1aaa5icjfkp0fu1ebo29e07.apps.googleusercontent.com.json
69 changes: 69 additions & 0 deletions how-to-use.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>How to Use - Coverquai</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<nav class="nav">
<div class="nav-container">
<a href="index.html" class="nav-logo">Coverquai</a>
<div class="nav-links">
<a href="index.html">Home</a>
<a href="how-to-use.html">How to Use</a>
<a href="tos.html">Terms of Service</a>
</div>
</div>
</nav>

<header>
<h1>How to Use Coverquai</h1>
<p>Follow these simple steps to generate your perfect cover letter</p>
</header>

<div class="container">
<div class="steps">
<div class="step-card">
<div class="step-content">
<h3>Step 1: Install the Extension</h3>
<p>Add Coverquai to Chrome from the Chrome Web Store. It's completely free!</p>
</div>
<div class="step-image-container">
<img src="screenshots/step1.png" alt="Installation step" class="step-image">
</div>
</div>

<div class="step-card">
<div class="step-content">
<h3>Step 2: Upload Your Resume</h3>
<p>Upload your resume once and set it as default for quick access.</p>
</div>
<div class="step-image-container">
<img src="screenshots/step2.png" alt="Resume upload step" class="step-image">
</div>
</div>

<div class="step-card">
<div class="step-content">
<h3>Step 3: Find a Job</h3>
<p>Navigate to any job posting on supported websites.</p>
<p>Currently supported websites: LinkedIn, Glassdoor, Workday, Eightfold AI</p>
</div>
</div>

<div class="step-card">
<div class="step-content">
<h3>Step 4: Generate Cover Letter and Download</h3>
<p>Click the Coverquai icon and watch as AI creates your personalized cover letter.</p>
<p>Download the cover letter as a PDF or copy the text directly.</p>
</div>
<div class="step-image-container">
<img src="screenshots/step4.png" alt="Generation step" class="step-image">
</div>
</div>
</div>
</div>
</body>
</html>
69 changes: 12 additions & 57 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,20 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Coverquai - Chrome Extension</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
margin: 0;
padding: 0;
color: #333;
}

header {
background: linear-gradient(135deg, #2196F3, #1976D2);
color: white;
padding: 4rem 2rem;
text-align: center;
}

.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}

.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin: 3rem 0;
}

.feature-card {
background: #fff;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

.cta-button {
display: inline-block;
background: #2196F3;
color: white;
padding: 1rem 2rem;
border-radius: 25px;
text-decoration: none;
font-weight: bold;
transition: background 0.3s;
}

.cta-button:hover {
background: #1976D2;
}

.demo-image {
max-width: 100%;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
</style>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<nav class="nav">
<div class="nav-container">
<a href="index.html" class="nav-logo">Coverquai</a>
<div class="nav-links">
<a href="index.html">Home</a>
<a href="how-to-use.html">How to Use</a>
<a href="tos.html">Terms of Service</a>
</div>
</div>
</nav>

<header>
<h1>Coverquai</h1>
<p>Generate tailored cover letters instantly using AI</p>
Expand Down
Binary file added screenshots/glassdoor_generation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/linkedin_generation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/options_page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/step1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/step2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/step4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 59 additions & 5 deletions src/backend/main.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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()

Expand All @@ -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
Expand Down Expand Up @@ -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 "):
Expand Down Expand Up @@ -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)):
Expand Down Expand Up @@ -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")
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))
82 changes: 81 additions & 1 deletion src/extension/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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') {
Expand All @@ -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',
Expand Down Expand Up @@ -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');
}
Loading

0 comments on commit 0d3b758

Please sign in to comment.