Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
649 changes: 416 additions & 233 deletions backend/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"graphql-scalars": "^1.22.4",
"jose": "^5.2.3",
"jsonwebtoken": "^9.0.2",
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.14",
"pg": "^8.11.3",
Expand Down
181 changes: 158 additions & 23 deletions backend/src/index copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { ApolloServerPluginDrainHttpServer } from "@apollo/server/plugin/drainHt
import cors from "cors";
import { buildSchema } from "type-graphql";
import { ContactResolver } from "./resolvers/contact.resolver";
import { GenerateImageResolver } from "./resolvers/generateImage.resolver";
import path from 'path';
import { CaptchaResolver } from './resolvers/captcha.resolver';
import { captchaImageMap, cleanUpExpiredCaptchas } from './CaptchaMap';
Expand All @@ -25,29 +24,42 @@ import "dotenv/config";
import { customAuthChecker } from "./lib/authChecker";
import { AdminResolver } from './resolvers/admin.resolver';
import { generateBadgeSvg } from './lib/badgeGenerator';
import { loadedLogos, loadLogos } from './lib/logoLoader';
import fs from "fs/promises";
import { authenticate } from "./middlewares/authenticate";
import { requireAdmin } from "./middlewares/requireAdmin";
import { createReadStream } from 'fs';

const prisma = new PrismaClient();

export interface JwtPayload {
userId: number;
id: number;
}

export interface MyContext {
req: express.Request;
res: express.Response;
apiKey: string | undefined;
cookies: Cookies;
token : string | undefined | null;
user: User | null;
}

// export interface JwtPayload {
// userId: number;
// email?: string;
// role?: string;
// iat?: number;
// exp?: number;
// }

const app = express();
const httpServer = http.createServer(app);

async function main() {
const schema = await buildSchema({
resolvers: [
ContactResolver,
GenerateImageResolver,
CaptchaResolver,
SkillResolver,
ProjectResolver,
Expand All @@ -71,16 +83,16 @@ async function main() {
const imageId = req.params.id;
const filename = captchaImageMap[imageId];
if (filename) {
const imagePath = path.join(__dirname, 'images', filename);
const imagePath = path.join(__dirname, 'images/captcha', filename);
res.sendFile(imagePath);
} else {
res.status(404).send('Image not found');
}
});

app.get('/badge/:label/:message/:messageColor/:labelColor', (req, res) => {
const { label, message, messageColor, labelColor } = req.params;
const { logo, logoColor, logoPosition } = req.query;
app.get('/badge/:label/:message/:messageColor/:labelColor/:logo', (req, res) => {
const { label, message, messageColor, labelColor, logo } = req.params;
const { logoColor, logoPosition } = req.query;

try {
const decodedLabel = decodeURIComponent(label);
Expand All @@ -91,34 +103,45 @@ async function main() {
const finalLogoPosition: 'left' | 'right' =
logoPosition === 'right' ? 'right' : 'left';

let logoDataForBadge: { base64: string; mimeType: string } | undefined;
if (logo) {
logoDataForBadge = loadedLogos.get(String(logo).toLowerCase());
if (!logoDataForBadge) {
console.warn(`Logo personnalisé '${logo}' non trouvé dans les logos chargés.`);
}
}

const svg = generateBadgeSvg(
decodedLabel,
decodedMessage,
decodedMessageColor,
decodedLabelColor,
logo ? String(logo) : undefined,
logoDataForBadge,
logoColor ? String(logoColor) : undefined,
finalLogoPosition
);

res.setHeader('Content-Type', 'image/svg+xml');
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.setHeader('Content-Type', 'image/svg+xml');
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.send(svg);
} catch (error) {
console.error("Erreur lors de la génération du badge SVG:", error);
res.status(500).send('<svg width="120" height="20" xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)"><rect width="120" height="20" fill="#E05D44"/><text x="5" y="14" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11px" fill="white">Error</text></svg>');
res.status(500).send('<svg width="120" height="20" xmlns="http://www.w3.org/2000/svg"><rect width="120" height="20" fill="#E05D44"/><text x="5" y="14" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11px" fill="white">Error</text></svg>');
}
});

app.get('/badge/stats/projects-count', async (req, res) => {
try {
const projectCount = await prisma.project.count();
const logoData = loadedLogos.get('github');
if (!logoData) console.warn("Logo 'github' non trouvé pour le badge projets.");

const svg = generateBadgeSvg(
'Projets',
String(projectCount),
'4CAF50',
'2F4F4F',
'JavaScript',
'2F4F4F',
logoData,
'white',
'right'
);
Expand All @@ -127,10 +150,121 @@ async function main() {
res.send(svg);
} catch (error) {
console.error("Erreur lors de la génération du badge des projets:", error);
res.status(500).send('<svg width="120" height="20" xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)"><rect width="120" height="20" fill="#E05D44"/><text x="5" y="14" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11px" fill="white">Error</text></svg>');
res.status(500).send('<svg width="120" height="20" xmlns="http://www.w3.org/2000/svg"><rect width="120" height="20" fill="#E05D44"/><text x="5" y="14" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11px" fill="white">Error</text></svg>');
}
});

app.get(
"/admin/backups",
authenticate,
requireAdmin,
async (req, res): Promise<void> => {
try {
const backupDir = path.resolve(__dirname, ".", "data");
const files = await fs.readdir(backupDir);

const backups = files.filter((f) =>
/^bdd_\d{8}_\d{6}\.sql$/i.test(f)
);

res.json(backups);
} catch (err) {
console.error("Erreur lecture des sauvegardes :", err);
res.status(500).send("Unable to read backups");
}
}
);

app.get(
"/admin/backups/:filename",
authenticate,
requireAdmin,
async (req, res): Promise<void> => {
try {
const backupDir = path.resolve(__dirname, ".", "data");
const filename = req.params.filename;

if (!filename) {
res.status(400).send("Missing file name");
return;
}
if (!/^bdd_\d{8}_\d{6}\.sql$/i.test(filename)) {
res.status(400).send("Invalid file name");
return;
}

const fullPath = path.join(backupDir, filename);

try {
await fs.access(fullPath);
} catch {
res.status(404).send("File not found");
return;
}

const content = await fs.readFile(fullPath, "utf-8");
res.type("text/plain").send(content);
} catch (err) {
console.error("Erreur lecture fichier backup :", err);
res.status(500).send("Unable to read save file");
}
}
);

app.get(
"/admin/backups/:filename/download",
authenticate,
requireAdmin,
async (req, res): Promise<void> => {
try {
const backupDir = path.resolve(__dirname, ".", "data");
const filename = req.params.filename;

if (!/^bdd_\d{8}_\d{6}\.sql$/i.test(filename)) {
res.status(400).send("Invalid file name");
return;
}

const fullPath = path.join(backupDir, filename);

try {
await fs.access(fullPath);
} catch {
res.status(404).send("File not found");
return;
}

res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
res.setHeader("Content-Type", "application/sql");

const fileStream = createReadStream(fullPath);
fileStream.pipe(res);
} catch (err) {
console.error("Erreur téléchargement fichier backup :", err);
res.status(500).send("Error while downloading backup file");
}
}
);

app.get('/upload/:type/:filename', (req, res) => {
const { type, filename } = req.params;

if (!['image', 'video'].includes(type)) {
return res.status(400).send('Invalid type. Use "image" or "video".');
}

const filePath = path.join(__dirname, '.', 'uploads', `${type}s`, filename);

res.sendFile(filePath, (err) => {
if (err) {
if (!res.headersSent) {
console.error(`Fichier non trouvé : ${filePath}`);
return res.status(404).send('Fichier non trouvé');
}
}
});
});

app.use(
"/graphql",
cors<cors.CorsRequest>({
Expand All @@ -141,23 +275,23 @@ async function main() {
expressMiddleware(server, {
context: async ({ req, res }) => {
const cookies = new Cookies(req, res);
console.log("cookies:", cookies.get("jwt"));

let user: User | null = null;

const token = cookies.get("jwt");
console.log("Token du cookie:", token ? "Présent" : "Absent");
const token = cookies.get("token");

if (token && process.env.JWT_SECRET) {
// console.log("token ---->", token)
try {
const { payload } = await jwtVerify<JwtPayload>(
token,
new TextEncoder().encode(process.env.JWT_SECRET)
);

console.log("Payload du token décodé:", payload);
// console.log("Payload du token décodé:", payload);

const prismaUser = await prisma.user.findUnique({
where: { id: payload.userId }
where: { id: payload.id }
});

if (prismaUser) {
Expand All @@ -172,8 +306,8 @@ async function main() {
}

} catch (err) {
console.error("Erreur de vérification JWT:", err); // Log l'erreur complète
cookies.set("jwt", "", { expires: new Date(0), httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax' as const });
console.error("Erreur de vérification JWT:", err);
cookies.set("token", "", { expires: new Date(0), httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax' as const });
}
}

Expand All @@ -190,7 +324,7 @@ async function main() {
await checkApiKey(apiKey);
}

return { req, res, apiKey, cookies, user };
return { req, res, apiKey, cookies, token, user };
},
})
);
Expand All @@ -203,4 +337,5 @@ async function main() {
console.log(`🚀 Server lancé sur http://localhost:4000/`);
}

main();
main();
loadLogos();
Loading