Add files via upload

This commit is contained in:
Arthur
2024-11-28 21:16:40 +01:00
committed by GitHub
parent db7bbff71c
commit 745c46f895
21 changed files with 3323 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('./users.db');
db.serialize(() => {
db.run(`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
status TEXT DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`);
db.run(`CREATE TABLE IF NOT EXISTS movies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
genre TEXT,
description TEXT,
cover TEXT,
video TEXT
)`);
db.run(`CREATE TABLE IF NOT EXISTS logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
action TEXT,
user_id INTEGER,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`);
});
module.exports = db;
View File
+7
View File
@@ -0,0 +1,7 @@
{
"title": "Titre du film",
"description": "Description du film",
"genre": "Genre du film",
"releaseDate": "Date de sortie",
"rating": "Note"
}
View File
+2500
View File
File diff suppressed because it is too large Load Diff
+16
View File
@@ -0,0 +1,16 @@
{
"name": "site-streaming",
"version": "1.0.0",
"description": "Site d'hébergement de films avec Vanilla JavaScript et Node.js",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"bcrypt": "^5.1.1",
"body-parser": "^1.20.3",
"express": "^4.21.1",
"jsonwebtoken": "^9.0.2",
"sqlite3": "^5.1.7"
}
}
+25
View File
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Administration</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<h1>Administration</h1>
<nav>
<a href="index.html">Accueil</a>
<button id="logout-button">Déconnexion</button>
</nav>
</header>
<main>
<div id="users-list">
<!-- Liste des utilisateurs -->
</div>
</main>
<script src="auth.js"></script>
<script src="admin.js"></script>
</body>
</html>
+87
View File
@@ -0,0 +1,87 @@
document.addEventListener('DOMContentLoaded', () => {
const token = localStorage.getItem('token');
fetch('/users', {
headers: {
'Authorization': token
}
})
.then(response => response.json())
.then(users => {
const usersList = document.getElementById('users-list');
users.forEach(user => {
const userElement = document.createElement('div');
userElement.classList.add('user');
userElement.innerHTML = `
<p>${user.email} - ${user.status}</p>
<button class="approve-button" data-id="${user.id}">Approuver</button>
<button class="delete-button" data-id="${user.id}">Supprimer</button>
<button class="admin-button" data-id="${user.id}">Rendre Admin</button>
`;
usersList.appendChild(userElement);
});
document.querySelectorAll('.approve-button').forEach(button => {
button.addEventListener('click', () => {
const userId = button.getAttribute('data-id');
fetch('/approve', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': token
},
body: JSON.stringify({ userId })
})
.then(response => response.json())
.then(data => {
alert(data.message);
window.location.reload();
});
});
});
document.querySelectorAll('.delete-button').forEach(button => {
button.addEventListener('click', () => {
const userId = button.getAttribute('data-id');
fetch('/delete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': token
},
body: JSON.stringify({ userId })
})
.then(response => response.json())
.then(data => {
alert(data.message);
window.location.reload();
});
});
});
document.querySelectorAll('.admin-button').forEach(button => {
button.addEventListener('click', () => {
const userId = button.getAttribute('data-id');
fetch('/make-admin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': token
},
body: JSON.stringify({ userId })
})
.then(response => response.json())
.then(data => {
alert(data.message);
window.location.reload();
});
});
});
});
const logoutButton = document.getElementById('logout-button');
logoutButton.addEventListener('click', () => {
localStorage.removeItem('token');
window.location.href = 'login.html';
});
});
+70
View File
@@ -0,0 +1,70 @@
const checkTokenValidity = () => {
const token = localStorage.getItem('token');
if (!token) return false;
return fetch('/users/me', {
headers: {
'Authorization': token
}
})
.then(response => {
if (!response.ok) {
localStorage.removeItem('token');
return false;
}
return true;
})
.catch(() => {
localStorage.removeItem('token');
return false;
});
};
document.addEventListener('DOMContentLoaded', async () => {
const token = localStorage.getItem('token');
const loginLink = document.getElementById('login-link');
const registerLink = document.getElementById('register-link');
const profileLink = document.getElementById('profile-link');
const adminLink = document.getElementById('admin-link');
const logoutButton = document.getElementById('logout-button');
if (token) {
const isValid = await checkTokenValidity();
if (!isValid) {
window.location.href = 'login.html';
return;
}
loginLink.style.display = 'none';
registerLink.style.display = 'none';
profileLink.style.display = 'block';
logoutButton.style.display = 'block';
fetch('/users/me', {
headers: {
'Authorization': token
}
})
.then(response => response.json())
.then(user => {
const welcomeMessage = document.getElementById('welcome-message');
if (welcomeMessage) {
welcomeMessage.textContent = `Bonjour ${user.email}`;
}
if (user.status === 'admin') {
adminLink.style.display = 'block';
}
});
logoutButton.addEventListener('click', () => {
localStorage.removeItem('token');
window.location.href = 'index.html';
});
} else {
loginLink.style.display = 'block';
registerLink.style.display = 'block';
profileLink.style.display = 'none';
adminLink.style.display = 'none';
logoutButton.style.display = 'none';
}
});
+30
View File
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Site de Streaming</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<h1>Site de Streaming</h1>
<nav>
<a href="index.html">Accueil</a>
<a href="login.html" id="login-link">Connexion</a>
<a href="register.html" id="register-link">Inscription</a>
<a href="profile.html" id="profile-link" style="display: none;">Profil</a>
<a href="admin.html" id="admin-link" style="display: none;">Administration</a>
<button id="logout-button" style="display: none;">Déconnexion</button>
</nav>
</header>
<main>
<h2 id="welcome-message"></h2>
<div id="movies-grid">
<!-- Grille des films/séries -->
</div>
</main>
<script src="auth.js"></script>
<script src="scripts.js"></script>
</body>
</html>
+31
View File
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connexion</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<h1>Connexion</h1>
<nav>
<a href="index.html">Accueil</a>
<a href="login.html" id="login-link">Connexion</a>
<a href="register.html" id="register-link">Inscription</a>
<button id="logout-button" style="display: none;">Déconnexion</button>
</nav>
</header>
<main>
<form id="login-form" class="auth-form">
<label for="email">Email :</label>
<input type="email" id="email" name="email" required>
<label for="password">Mot de passe :</label>
<input type="password" id="password" name="password" required>
<button type="submit">Connexion</button>
</form>
</main>
<script src="auth.js"></script>
<script src="login.js"></script>
</body>
</html>
+22
View File
@@ -0,0 +1,22 @@
document.getElementById('login-form').addEventListener('submit', function(event) {
event.preventDefault();
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, password })
})
.then(response => response.json())
.then(data => {
if (data.token) {
localStorage.setItem('token', data.token);
window.location.href = 'index.html';
} else {
alert('Email ou mot de passe incorrect');
}
});
});
+31
View File
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Détails du Film</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<h1>Détails du Film</h1>
<nav>
<a href="index.html">Accueil</a>
<a href="login.html" id="login-link">Connexion</a>
<a href="register.html" id="register-link">Inscription</a>
<button id="logout-button" style="display: none;">Déconnexion</button>
</nav>
</header>
<main>
<div id="movie-details">
<!-- Détails du film -->
</div>
<video id="movie-video" controls>
<source id="movie-source" type="video/mp4">
Votre navigateur ne supporte pas la lecture de vidéos.
</video>
</main>
<script src="auth.js"></script>
<script src="movie.js"></script>
</body>
</html>
+44
View File
@@ -0,0 +1,44 @@
document.addEventListener('DOMContentLoaded', () => {
const urlParams = new URLSearchParams(window.location.search);
const movieTitle = urlParams.get('title');
const token = localStorage.getItem('token');
if (!token) {
alert('Vous devez avoir un compte pour voir ce film.');
window.location.href = 'login.html';
return;
}
fetch('/users/me', {
headers: {
'Authorization': token
}
})
.then(response => response.json())
.then(user => {
console.log(user)
if (user.status !== 'approved' && user.status !== 'admin') {
alert('Votre compte n\'a pas encore été approuvé par un administrateur.');
window.location.href = 'index.html';
} else {
fetch(`/movie/${encodeURIComponent(movieTitle)}`, {
headers: {
'Authorization': token
}
})
.then(response => response.json())
.then(movie => {
const movieDetails = document.getElementById('movie-details');
movieDetails.innerHTML = `
<h2>${movie.title}</h2>
<p>${movie.description}</p>
<img src="${movie.cover}" alt="${movie.title}">
`;
const movieSource = document.getElementById('movie-source');
movieSource.src = movie.video;
const movieVideo = document.getElementById('movie-video');
movieVideo.load();
});
}
});
});
+29
View File
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Profil Utilisateur</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<h1>Profil Utilisateur</h1>
<nav>
<a href="index.html">Accueil</a>
<a href="login.html" id="login-link">Connexion</a>
<a href="register.html" id="register-link">Inscription</a>
<a href="profile.html" id="profile-link" style="display: none;">Profil</a>
<a href="admin.html" id="admin-link" style="display: none;">Administration</a>
<button id="logout-button" style="display: none;">Déconnexion</button>
</nav>
</header>
<main>
<div id="profile-details">
<!-- Détails du profil utilisateur -->
</div>
</main>
<script src="auth.js"></script>
<script src="profile.js"></script>
</body>
</html>
+23
View File
@@ -0,0 +1,23 @@
document.addEventListener('DOMContentLoaded', () => {
const token = localStorage.getItem('token');
if (!token) {
alert('Vous devez être connecté pour accéder à votre profil.');
window.location.href = 'login.html';
return;
}
fetch('/profile', {
headers: {
'Authorization': token
}
})
.then(response => response.json())
.then(user => {
const profileDetails = document.getElementById('profile-details');
profileDetails.innerHTML = `
<h2>Profil de ${user.email}</h2>
<p>Status: ${user.status}</p>
`;
});
});
+31
View File
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Inscription</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<h1>Inscription</h1>
<nav>
<a href="index.html">Accueil</a>
<a href="login.html" id="login-link">Connexion</a>
<a href="register.html" id="register-link">Inscription</a>
<button id="logout-button" style="display: none;">Déconnexion</button>
</nav>
</header>
<main>
<form id="register-form" class="auth-form">
<label for="email">Email :</label>
<input type="email" id="email" name="email" required>
<label for="password">Mot de passe :</label>
<input type="password" id="password" name="password" required>
<button type="submit">Inscription</button>
</form>
</main>
<script src="auth.js"></script>
<script src="register.js"></script>
</body>
</html>
+22
View File
@@ -0,0 +1,22 @@
document.getElementById('register-form').addEventListener('submit', function(event) {
event.preventDefault();
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
fetch('/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, password })
})
.then(response => response.json())
.then(data => {
if (data.token) {
localStorage.setItem('token', data.token);
window.location.href = 'index.html';
} else {
alert('Erreur lors de l\'inscription');
}
});
});
+19
View File
@@ -0,0 +1,19 @@
document.addEventListener('DOMContentLoaded', () => {
fetch('/movies')
.then(response => response.json())
.then(movies => {
const moviesGrid = document.getElementById('movies-grid');
movies.forEach(movie => {
const movieElement = document.createElement('div');
movieElement.classList.add('movie');
movieElement.innerHTML = `
<img src="${movie.cover}" alt="${movie.title}">
<h3>${movie.title}</h3>
`;
movieElement.addEventListener('click', () => {
window.location.href = `movie.html?title=${encodeURIComponent(movie.title)}`;
});
moviesGrid.appendChild(movieElement);
});
});
});
+92
View File
@@ -0,0 +1,92 @@
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}
header {
background-color: #333;
color: #fff;
padding: 1rem;
text-align: center;
}
nav a {
color: #fff;
margin: 0 1rem;
text-decoration: none;
}
nav button {
background-color: #333;
color: #fff;
border: none;
cursor: pointer;
}
main {
padding: 2rem;
}
.auth-form {
max-width: 400px;
margin: 0 auto;
padding: 1rem;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 5px;
}
.auth-form label {
display: block;
margin-bottom: 0.5rem;
}
.auth-form input {
width: 100%;
padding: 0.5rem;
margin-bottom: 1rem;
border: 1px solid #ccc;
border-radius: 5px;
}
.auth-form button {
width: 100%;
padding: 0.5rem;
background-color: #333;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
}
#movies-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 1rem;
}
.movie {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 5px;
padding: 1rem;
text-align: center;
cursor: pointer;
}
.movie img {
max-width: 100%;
border-radius: 5px;
}
#movie-details {
text-align: center;
}
#movie-video {
display: block;
margin: 2rem auto;
max-width: 100%;
}
+216
View File
@@ -0,0 +1,216 @@
const express = require('express');
const bodyParser = require('body-parser');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const db = require('./database');
const fs = require('fs');
const path = require('path');
const app = express();
const port = 3000;
app.use(bodyParser.json());
app.use(express.static('public'));
app.use('/movies', express.static(path.join(__dirname, 'movies')));
const authenticateToken = (req, res, next) => {
const token = req.headers['authorization'];
if (!token) return res.sendStatus(401);
jwt.verify(token, 'secret_key', (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
};
const checkApproval = (req, res, next) => {
db.get(`SELECT status FROM users WHERE id = ?`, [req.user.id], (err, user) => {
if (err || !user || (user.status !== 'approved' && user.status !== 'admin')) {
return res.status(403).json({ message: 'Votre compte n\'a pas encore été approuvé par un administrateur.' });
}
next();
});
};
const checkAdmin = (req, res, next) => {
db.get(`SELECT status FROM users WHERE id = ?`, [req.user.id], (err, user) => {
if (err || !user || user.status !== 'admin') {
return res.status(403).json({ message: 'Accès réservé aux administrateurs.' });
}
next();
});
};
// Route pour afficher la liste des films
app.get('/movies', (req, res) => {
const moviesDir = path.join(__dirname, 'movies');
fs.readdir(moviesDir, (err, files) => {
if (err) {
return res.status(500).json({ message: 'Erreur lors de la lecture des films' });
}
const movies = files.map(file => {
const moviePath = path.join(moviesDir, file);
const movieData = JSON.parse(fs.readFileSync(path.join(moviePath, 'info.json')));
return {
title: movieData.title,
cover: `/movies/${file}/cover.jpg`
};
});
res.json(movies);
});
});
app.get('/movie/:title', (req, res) => {
const movieTitle = req.params.title;
const moviesDir = path.join(__dirname, 'movies');
fs.readdir(moviesDir, (err, files) => {
if (err) {
return res.status(500).json({ message: 'Erreur lors de la lecture des films' });
}
const movieFile = files.find(file => {
const moviePath = path.join(moviesDir, file);
const movieData = JSON.parse(fs.readFileSync(path.join(moviePath, 'info.json')));
return movieData.title === movieTitle;
});
if (!movieFile) {
return res.status(404).json({ message: 'Film non trouvé' });
}
const moviePath = path.join(moviesDir, movieFile);
const movieData = JSON.parse(fs.readFileSync(path.join(moviePath, 'info.json')));
movieData.cover = `/movies/${movieFile}/cover.jpg`;
movieData.video = `/movies/${movieFile}/video.mp4`;
res.json(movieData);
});
});
// Route pour gérer l'inscription des utilisateurs
app.post('/register', (req, res) => {
const { email, password } = req.body;
const hashedPassword = bcrypt.hashSync(password, 10);
db.run(`INSERT INTO users (email, password) VALUES (?, ?)`, [email, hashedPassword], function(err) {
if (err) {
return res.status(500).json({ message: 'Erreur lors de l\'inscription' });
}
const userId = this.lastID;
const token = jwt.sign({ id: userId }, 'secret_key', { expiresIn: '1h' });
res.status(201).json({ message: 'Utilisateur inscrit avec succès', token });
});
});
// Route pour gérer la connexion des utilisateurs
app.post('/login', (req, res) => {
const { email, password } = req.body;
db.get(`SELECT * FROM users WHERE email = ?`, [email], (err, user) => {
if (err || !user || !bcrypt.compareSync(password, user.password)) {
return res.status(401).json({ message: 'Email ou mot de passe incorrect' });
}
const token = jwt.sign({ id: user.id }, 'secret_key', { expiresIn: '1h' });
res.json({ token });
});
});
app.get('/users/me', authenticateToken, (req, res) => {
db.get(`SELECT email, status FROM users WHERE id = ?`, [req.user.id], (err, user) => {
if (err || !user) {
return res.status(500).json({ message: 'Erreur lors de la récupération des informations utilisateur' });
}
res.json(user);
});
});
// Route pour récupérer tous les utilisateurs
app.get('/users', authenticateToken, checkAdmin, (req, res) => {
db.all(`SELECT id, email, status FROM users`, (err, users) => {
if (err) {
return res.status(500).json({ message: 'Erreur lors de la récupération des utilisateurs' });
}
res.json(users);
});
});
// Route pour approuver un utilisateur
app.post('/approve', authenticateToken, checkAdmin, (req, res) => {
const { userId } = req.body;
db.run(`UPDATE users SET status = 'approved' WHERE id = ?`, [userId], function(err) {
if (err) {
return res.status(500).json({ message: 'Erreur lors de l\'approbation de l\'utilisateur' });
}
res.json({ message: 'Utilisateur approuvé avec succès' });
});
});
// Route pour supprimer un utilisateur
app.post('/delete', authenticateToken, checkAdmin, (req, res) => {
const { userId } = req.body;
db.run(`DELETE FROM users WHERE id = ?`, [userId], function(err) {
if (err) {
return res.status(500).json({ message: 'Erreur lors de la suppression de l\'utilisateur' });
}
res.json({ message: 'Utilisateur supprimé avec succès' });
});
});
// Route pour promouvoir un utilisateur en administrateur
app.post('/make-admin', authenticateToken, checkAdmin, (req, res) => {
const { userId } = req.body;
db.run(`UPDATE users SET status = 'admin' WHERE id = ?`, [userId], function(err) {
if (err) {
return res.status(500).json({ message: 'Erreur lors de la promotion de l\'utilisateur' });
}
res.json({ message: 'Utilisateur promu administrateur avec succès' });
});
});
// Route pour lire les fichiers vidéo
app.get('/video/:filename', authenticateToken, checkApproval, (req, res) => {
const filePath = path.join(__dirname, 'movies', req.params.filename, 'video.mp4');
const stat = fs.statSync(filePath);
const fileSize = stat.size;
const range = req.headers.range;
if (range) {
const parts = range.replace(/bytes=/, "").split("-");
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
const chunksize = (end - start) + 1;
const file = fs.createReadStream(filePath, { start, end });
const head = {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
'Content-Type': 'video/mp4',
};
res.writeHead(206, head);
file.pipe(res);
} else {
const head = {
'Content-Length': fileSize,
'Content-Type': 'video/mp4',
};
res.writeHead(200, head);
fs.createReadStream(filePath).pipe(res);
}
});
app.get('/profile', authenticateToken, (req, res) => {
db.get(`SELECT email, status FROM users WHERE id = ?`, [req.user.id], (err, user) => {
if (err || !user) {
return res.status(500).json({ message: 'Erreur lors de la récupération des informations utilisateur' });
}
res.json(user);
});
});
app.listen(port, () => {
console.log(`Serveur démarré sur http://localhost:${port}`);
});