Fixed .pdf resume upload, Logout button background
This commit is contained in:
parent
49b03eb083
commit
7f71b0357f
@ -8,11 +8,12 @@ import sqlite3 from 'sqlite3';
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs/promises';
|
||||||
import multer from 'multer';
|
import multer from 'multer';
|
||||||
import mammoth from 'mammoth';
|
import mammoth from 'mammoth';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.js';
|
import pkg from 'pdfjs-dist';
|
||||||
|
|
||||||
import OpenAI from 'openai';
|
import OpenAI from 'openai';
|
||||||
|
|
||||||
// --- Basic file init ---
|
// --- Basic file init ---
|
||||||
@ -28,6 +29,7 @@ dotenv.config({ path: envPath }); // Load .env file
|
|||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PREMIUM_PORT || 5002;
|
const PORT = process.env.PREMIUM_PORT || 5002;
|
||||||
|
|
||||||
|
const { getDocument } = pkg;
|
||||||
|
|
||||||
let db;
|
let db;
|
||||||
const initDB = async () => {
|
const initDB = async () => {
|
||||||
@ -1655,6 +1657,22 @@ ${resumeText}
|
|||||||
Precisely Tailored, ATS-Optimized Resume:
|
Precisely Tailored, ATS-Optimized Resume:
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
async function extractTextFromPDF(filePath) {
|
||||||
|
const fileBuffer = await fs.readFile(filePath);
|
||||||
|
const uint8Array = new Uint8Array(fileBuffer); // Convert Buffer explicitly
|
||||||
|
const pdfDoc = await getDocument({ data: uint8Array }).promise;
|
||||||
|
|
||||||
|
let text = '';
|
||||||
|
for (let pageNum = 1; pageNum <= pdfDoc.numPages; pageNum++) {
|
||||||
|
const page = await pdfDoc.getPage(pageNum);
|
||||||
|
const pageText = await page.getTextContent();
|
||||||
|
text += pageText.items.map(item => item.str).join(' ');
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Your corrected endpoint with limits correctly returned:
|
||||||
app.post(
|
app.post(
|
||||||
'/api/premium/resume/optimize',
|
'/api/premium/resume/optimize',
|
||||||
upload.single('resumeFile'),
|
upload.single('resumeFile'),
|
||||||
@ -1668,7 +1686,6 @@ app.post(
|
|||||||
|
|
||||||
const userId = req.userId;
|
const userId = req.userId;
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const currentWeek = getWeekNumber(now); // Function defined below
|
|
||||||
|
|
||||||
const userProfile = await db.get(
|
const userProfile = await db.get(
|
||||||
`SELECT is_premium, is_pro_premium, resume_optimizations_used, resume_limit_reset, resume_booster_count
|
`SELECT is_premium, is_pro_premium, resume_optimizations_used, resume_limit_reset, resume_booster_count
|
||||||
@ -1702,16 +1719,20 @@ app.post(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const filePath = req.file.path;
|
const filePath = req.file.path;
|
||||||
const fileExt = req.file.originalname.split('.').pop().toLowerCase();
|
const mimeType = req.file.mimetype;
|
||||||
|
|
||||||
let resumeText = '';
|
let resumeText = '';
|
||||||
if (fileExt === 'pdf') {
|
if (mimeType === 'application/pdf') {
|
||||||
resumeText = await extractTextFromPDF(filePath);
|
resumeText = await extractTextFromPDF(filePath);
|
||||||
} else if (fileExt === 'docx') {
|
} else if (
|
||||||
|
mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
|
||||||
|
mimeType === 'application/msword'
|
||||||
|
) {
|
||||||
const result = await mammoth.extractRawText({ path: filePath });
|
const result = await mammoth.extractRawText({ path: filePath });
|
||||||
resumeText = result.value;
|
resumeText = result.value;
|
||||||
} else {
|
} else {
|
||||||
return res.status(400).json({ error: 'Unsupported file type.' });
|
await fs.unlink(filePath);
|
||||||
|
return res.status(400).json({ error: 'Unsupported or corrupted file upload.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const prompt = buildResumePrompt(resumeText, jobTitle, jobDescription);
|
const prompt = buildResumePrompt(resumeText, jobTitle, jobDescription);
|
||||||
@ -1728,12 +1749,16 @@ app.post(
|
|||||||
`UPDATE user_profile SET resume_optimizations_used = resume_optimizations_used + 1 WHERE user_id = ?`,
|
`UPDATE user_profile SET resume_optimizations_used = resume_optimizations_used + 1 WHERE user_id = ?`,
|
||||||
[userId]
|
[userId]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Calculate remaining optimizations
|
|
||||||
const remainingOptimizations = totalLimit - (userProfile.resume_optimizations_used + 1);
|
const remainingOptimizations = totalLimit - (userProfile.resume_optimizations_used + 1);
|
||||||
|
|
||||||
fs.unlinkSync(filePath);
|
await fs.unlink(filePath);
|
||||||
res.json({ optimizedResume, remainingOptimizations });
|
res.json({
|
||||||
|
optimizedResume,
|
||||||
|
remainingOptimizations,
|
||||||
|
resetDate: resetDate.toISOString() // <-- explicitly returned here!
|
||||||
|
});
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error optimizing resume:', err);
|
console.error('Error optimizing resume:', err);
|
||||||
res.status(500).json({ error: 'Failed to optimize resume.' });
|
res.status(500).json({ error: 'Failed to optimize resume.' });
|
||||||
|
12
src/App.js
12
src/App.js
@ -236,12 +236,12 @@ function App() {
|
|||||||
|
|
||||||
{/* Logout */}
|
{/* Logout */}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
className="text-red-600 hover:text-red-800"
|
className="text-red-600 hover:text-red-800 bg-transparent border-none"
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
>
|
>
|
||||||
Logout
|
Logout
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -9,6 +9,7 @@ function ResumeRewrite() {
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [remainingOptimizations, setRemainingOptimizations] = useState(null);
|
const [remainingOptimizations, setRemainingOptimizations] = useState(null);
|
||||||
const [resetDate, setResetDate] = useState(null);
|
const [resetDate, setResetDate] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(false); // ADDED loading state
|
||||||
|
|
||||||
const handleFileChange = (e) => {
|
const handleFileChange = (e) => {
|
||||||
setResumeFile(e.target.files[0]);
|
setResumeFile(e.target.files[0]);
|
||||||
@ -39,6 +40,8 @@ function ResumeRewrite() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLoading(true); // ACTIVATE loading
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@ -55,12 +58,12 @@ function ResumeRewrite() {
|
|||||||
|
|
||||||
setOptimizedResume(res.data.optimizedResume || '');
|
setOptimizedResume(res.data.optimizedResume || '');
|
||||||
setError('');
|
setError('');
|
||||||
|
|
||||||
// Refresh remaining optimizations after optimizing
|
|
||||||
fetchRemainingOptimizations();
|
fetchRemainingOptimizations();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Resume optimization error:', err);
|
console.error('Resume optimization error:', err);
|
||||||
setError(err.response?.data?.error || 'Failed to optimize resume.');
|
setError(err.response?.data?.error || 'Failed to optimize resume.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false); // DEACTIVATE loading
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -101,10 +104,22 @@ function ResumeRewrite() {
|
|||||||
|
|
||||||
{error && <p className="text-red-600 font-semibold">{error}</p>}
|
{error && <p className="text-red-600 font-semibold">{error}</p>}
|
||||||
|
|
||||||
<button type="submit"
|
<button
|
||||||
className="inline-block bg-blue-600 text-white font-semibold px-5 py-2 rounded hover:bg-blue-700 transition-colors">
|
type="submit"
|
||||||
Optimize Resume
|
disabled={loading}
|
||||||
|
className={`inline-block font-semibold px-5 py-2 rounded transition-colors ${
|
||||||
|
loading ? 'bg-gray-400 cursor-not-allowed' : 'bg-blue-600 hover:bg-blue-700'
|
||||||
|
} text-white`}
|
||||||
|
>
|
||||||
|
{loading ? 'Optimizing Resume...' : 'Optimize Resume'}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{loading && (
|
||||||
|
<div className="flex items-center justify-center mt-4">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
|
||||||
|
<span className="ml-3 text-blue-700 font-semibold">Optimizing your resume...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{optimizedResume && (
|
{optimizedResume && (
|
||||||
|
BIN
uploads/27bb962c438a8ffe986fc7571948bfb7
Normal file
BIN
uploads/27bb962c438a8ffe986fc7571948bfb7
Normal file
Binary file not shown.
BIN
uploads/7494904b7764d12ecd6900f9292949f2
Normal file
BIN
uploads/7494904b7764d12ecd6900f9292949f2
Normal file
Binary file not shown.
BIN
uploads/9fce92f8988efce8891352b7d64b2829
Normal file
BIN
uploads/9fce92f8988efce8891352b7d64b2829
Normal file
Binary file not shown.
BIN
uploads/b4299293be37c7a453d27801078a4efb
Normal file
BIN
uploads/b4299293be37c7a453d27801078a4efb
Normal file
Binary file not shown.
BIN
uploads/d0470483ed235e9d1a88bee2005dd840
Normal file
BIN
uploads/d0470483ed235e9d1a88bee2005dd840
Normal file
Binary file not shown.
BIN
user_profile.db
BIN
user_profile.db
Binary file not shown.
Loading…
Reference in New Issue
Block a user