Compare commits

..

No commits in common. "feature/initialize" and "master" have entirely different histories.

13 changed files with 0 additions and 392 deletions

27
.gitignore vendored
View file

@ -1,27 +0,0 @@
# Node modules
node_modules/
# Dependency lock files
package-lock.json
yarn.lock
pnpm-lock.yaml
# Environment variables
.env
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# OS files
.DS_Store
Thumbs.db
# VS Code settings
.vscode/
# Build output
dist/
build/

View file

@ -1,38 +0,0 @@
# CVGen Project
## Overview
CVGen is a Node.js application that connects to the LinkedIn API to retrieve user profile information. The application is designed to display this information on a blank page without the need for HTML or CSS.
## Project Structure
```
cvgen
├── controllers
│ └── linkedin.js # Handles LinkedIn API connection and user profile retrieval
├── routes
│ ├── api.routes.js # Defines API routes for LinkedIn authentication and data fetching
│ └── front.routes.js # Defines front-end routes (currently not utilized)
├── views # Currently empty, not needed for this project
├── public
│ └── img # Contains images (not utilized in this project)
├── app.js # Entry point of the application, sets up server and routes
├── package.json # Configuration file for npm, lists project dependencies
└── README.md # Documentation for the project
```
## Setup Instructions
1. Clone the repository to your local machine.
2. Navigate to the project directory.
3. Run `npm install` to install the required dependencies.
4. Configure your LinkedIn API credentials in the `linkedin.js` controller.
5. Start the application by running `node app.js`.
## Usage
- The application will connect to the LinkedIn API and retrieve user profile information.
- The retrieved information will be displayed on a blank page.
## Dependencies
- Express: A web framework for Node.js.
- LinkedIn API libraries: For connecting and interacting with the LinkedIn API.
## License
This project is licensed under the MIT License.

26
app.js
View file

@ -1,26 +0,0 @@
const express = require("express");
const bodyParser = require("body-parser");
const session = require("express-session");
const apiRoutes = require("./routes/api.routes");
const frontRoutes = require("./routes/front.routes");
const app = express();
const PORT = process.env.PORT || 4200;
// Middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(session({
secret: "your_secret_key", // change ce secret !
resave: false,
saveUninitialized: true,
}));
// Routes
app.use("/api", apiRoutes);
app.use("/", frontRoutes);
// Start the server
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});

View file

@ -1,204 +0,0 @@
// filepath: c:\Users\amaizy\Desktop\cvgen\controllers\linkedin.js
const axios = require("axios");
const querystring = require("querystring");
const { chromium } = require("playwright");
const clientId = "780w7gsy8eysmj";
const clientSecret = "WPL_AP1.w6OTTkAndAdT3PYF.UZEcwQ==";
const redirectUri = "http://localhost:4200/api/auth/linkedin/callback";
const scope = "openid profile email";
const email = "amaury@maizy.net";
const password = "2Qh*fJrp+l7M6g>8P~}/S$Bc2Yvf&-vd";
let accessToken = "";
const authenticateUser = (req, res) => {
const authUrl = `https://www.linkedin.com/oauth/v2/authorization?${querystring.stringify(
{
response_type: "code",
client_id: clientId,
redirect_uri: redirectUri,
scope: scope,
}
)}`;
res.redirect(authUrl);
};
const handleCallback = async (req, res) => {
const { code } = req.query;
try {
const tokenResponse = await axios.post(
"https://www.linkedin.com/oauth/v2/accessToken",
querystring.stringify({
grant_type: "authorization_code",
code: code,
redirect_uri: redirectUri,
client_id: clientId,
client_secret: clientSecret,
})
);
const accessToken = tokenResponse.data.access_token;
// Stocke dans la session
req.session.user = {
accessToken,
};
// Redirige vers /profile après authentification
res.redirect("/profile");
} catch (error) {
console.error(
"LinkedIn token error:",
error.response ? error.response.data : error.message
);
res.status(500).send("Error retrieving access token.");
}
};
const getUserProfile = async (req, res) => {
try {
const token = req.session?.user?.accessToken;
if (!token) {
return res.status(401).json({ error: "Aucun accessToken en session." });
}
const { data } = await axios.get("https://api.linkedin.com/v2/userinfo", {
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
},
});
// Enregistre les infos utiles en session
req.session.user.nom = data.given_name + " " + data.family_name;
req.session.user.img = data.picture;
req.session.user.email = data.email;
// Redirige vers /me après authentification
res.redirect("/me");
} catch (error) {
console.error(
"LinkedIn OIDC error:",
error.response?.data || error.message
);
return res
.status(500)
.json({ error: "Erreur lors de la récupération du profil." });
}
};
const scrapeLinkedInProfile = async (user) => {
const UserImg = user.img;
const UserEmail = user.email;
const profileUrl = user.linkedinUrl;
console.log("Scraping LinkedIn profile:", profileUrl);
const browser = await chromium.launch({
headless: true,
args: ["--no-sandbox", "--disable-setuid-sandbox"],
});
const page = await browser.newPage();
try {
// === 1. Login ===
await page.goto("https://www.linkedin.com/login", {
waitUntil: "domcontentloaded",
timeout: 60000,
});
console.log("Login page loaded");
await page.screenshot({
path: "public/error/step1_login.png",
fullPage: true,
});
await page.fill("#username", email, { delay: 50 });
await page.fill("#password", password, { delay: 50 });
await Promise.all([
page.click('[type="submit"]'),
page.waitForNavigation({ waitUntil: "domcontentloaded", timeout: 60000 }),
]);
console.log("Logged in");
await page.screenshot({
path: "public/error/step2_logged_in.png",
fullPage: true,
});
// === 2. Aller sur le profil ===
await page.goto(profileUrl, {
waitUntil: "domcontentloaded",
timeout: 60000,
});
await page.waitForSelector("h1", { timeout: 60000 });
console.log("Profile page loaded");
await page.screenshot({
path: "public/error/step3_profile_loaded.png",
fullPage: true,
});
// Scroll pour charger contenu lazy
await page.evaluate(() => window.scrollBy(0, window.innerHeight));
await page.waitForTimeout(2000);
await page.screenshot({
path: "public/error/step4_scrolled.png",
fullPage: true,
});
// === 3. Extraire les infos ===
const profileData = await page.evaluate(() => {
const getText = (selector) =>
document.querySelector(selector)?.innerText || null;
const getAllText = (selector) =>
Array.from(document.querySelectorAll(selector)).map((el) =>
el.innerText.trim()
);
return {
name: getText("h1"),
headline: getText("div.text-body-medium.break-words"),
location: getText(
"span.text-body-small.inline.t-black--light.break-words"
),
// about: getText(""),
experiences: getAllText(
"div.KbuWagYZEALUPOYGtkFczhgTiHHNRLZKhdLlK > ul li"
),
// education: getAllText(""),
// skills: getAllText(""),
};
});
// Ajoute les infos supplémentaires avant de retourner
profileData.img = UserImg || null;
profileData.email = UserEmail || null;
profileData.linkedinUrl = profileUrl || null;
await page.screenshot({
path: "public/error/step5_data_extracted.png",
fullPage: true,
});
console.log("Data extracted:", profileData);
await browser.close();
return profileData;
} catch (err) {
console.error("❌ Erreur pendant le scraping:", err.message);
await page.screenshot({ path: "public/error/error.png", fullPage: true });
await browser.close();
throw err;
}
};
module.exports = {
authenticateUser,
handleCallback,
getUserProfile,
scrapeLinkedInProfile,
};

View file

@ -1,22 +0,0 @@
{
"name": "cvgen",
"version": "1.0.0",
"description": "A project to connect to the LinkedIn API and retrieve user profile information.",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"axios": "^1.11.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-session": "^1.17.1",
"playwright": "^1.55.0"
},
"author": "ExostFlash",
"license": "ISC",
"repository": {
"type": "git",
"url": "ssh://git@git.lehub.tf:2222/MecDu.Dev/cvgen.git"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 907 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 636 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 888 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 899 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 906 KiB

View file

@ -1,23 +0,0 @@
// filepath: c:\Users\amaizy\Desktop\cvgen\routes\api.routes.js
const express = require("express");
const router = express.Router();
const { authenticateUser, handleCallback } = require("../controllers/linkedin");
// Route for LinkedIn authentication
router.get("/auth/linkedin", authenticateUser);
// Callback route for LinkedIn OAuth2
router.get("/auth/linkedin/callback", handleCallback);
// Traitement du formulaire d'URL LinkedIn
router.post("/me/link", express.urlencoded({ extended: true }), (req, res) => {
if (!req.session.user) return res.redirect("/");
req.session.user.linkedinUrl = req.body.linkedinUrl;
res.send(`
<p>URL LinkedIn enregistrée : <a href="${req.body.linkedinUrl}" target="_blank">${req.body.linkedinUrl}</a></p>
<a href="/me">Retour</a>
`);
});
module.exports = router;

View file

@ -1,52 +0,0 @@
// filepath: c:\Users\amaizy\Desktop\cvgen\routes\front.routes.js
const express = require("express");
const router = express.Router();
const {
getUserProfile,
scrapeLinkedInProfile,
} = require("../controllers/linkedin");
// Page d'accueil
router.get("/", (req, res) => {
const user = req.session.user;
if (user) {
return res.redirect("/me");
}
res.send(`
<h1>Welcome to the CV Generator Home Page!</h1>
<a href="/api/auth/linkedin">
<button>Connexion LinkedIn</button>
</a>
`);
});
// Route pour afficher le profil utilisateur (API)
router.get("/profile", getUserProfile);
// Route protégée /me
router.get("/me", async (req, res) => {
const user = req.session.user;
if (!user) {
return res.redirect("/");
}
if (!user.linkedinUrl) {
return res.send(`
<h2>Bienvenue, ${user.nom} !</h2>
<form method="POST" action="/api/me/link">
<label for="linkedinUrl">Votre URL LinkedIn :</label>
<input type="url" id="linkedinUrl" name="linkedinUrl" placeholder="https://www.linkedin.com/in/votre-profil" required>
<button type="submit">Enregistrer</button>
</form>
`);
}
const profile = await scrapeLinkedInProfile(user);
res.send(profile);
});
module.exports = router;