committed by
GitHub
10 changed files with 271 additions and 12 deletions
@ -0,0 +1,90 @@ |
|||
import {getClient} from "../utils/database.js"; |
|||
|
|||
export async function search(req, res) { |
|||
try { |
|||
console.log(req.query); |
|||
const query = req.query.q; |
|||
const type = req.query.type || 'all'; |
|||
const offset = req.query.offset || 0; |
|||
const limit = req.query.limit || 20; |
|||
const client = await getClient(); |
|||
|
|||
|
|||
if (!query) { |
|||
return res.status(400).json({ message: "Query parameter 'q' is required" }); |
|||
} |
|||
|
|||
if (type === 'videos') { |
|||
// Search video in database based on the query, video title, tags and author
|
|||
const videoNameQuery = `SELECT id FROM videos WHERE title ILIKE $1 OFFSET $3 LIMIT $2`; |
|||
const videoNameResult = await client.query(videoNameQuery, [`%${query}%`, limit, offset]); |
|||
|
|||
// Search video from tags
|
|||
const tagQuery = `SELECT id FROM tags WHERE name ILIKE $1 OFFSET $3 LIMIT $2`; |
|||
const tagResult = await client.query(tagQuery, [`%${query}%`, limit, offset]); |
|||
const tags = tagResult.rows.map(tag => tag.name); |
|||
|
|||
for (const tag of tags) { |
|||
const videoTagQuery = `SELECT id FROM videos WHERE id IN (SELECT video FROM video_tags WHERE tag = (SELECT id FROM tags WHERE name = $1)) OFFSET $3 LIMIT $2`; |
|||
const videoTagResult = await client.query(videoTagQuery, [tag, limit, offset]); |
|||
videoNameResult.rows.push(...videoTagResult.rows); |
|||
} |
|||
|
|||
// Search video from author
|
|||
const authorQuery = `SELECT videos.id FROM videos JOIN channels c ON videos.channel = c.id WHERE c.name ILIKE $1`; |
|||
const authorResult = await client.query(authorQuery, [`%${query}%`]); |
|||
|
|||
for (const author of authorResult.rows) { |
|||
if (!videoNameResult.rows.some(video => video.id === author.id)) { |
|||
videoNameResult.rows.push(author); |
|||
} |
|||
} |
|||
|
|||
const videos = []; |
|||
|
|||
for (let video of videoNameResult.rows) { |
|||
video = video.id; // Extracting the video ID
|
|||
let videoDetails = {}; |
|||
|
|||
// Fetching video details
|
|||
const videoDetailsQuery = `SELECT id, title, description, thumbnail, channel, release_date FROM videos WHERE id = $1`; |
|||
const videoDetailsResult = await client.query(videoDetailsQuery, [video]); |
|||
if (videoDetailsResult.rows.length === 0) { |
|||
continue; // Skip if no video details found
|
|||
} |
|||
|
|||
videoDetails = videoDetailsResult.rows[0]; |
|||
// Setting the type
|
|||
videoDetails.type = 'video'; |
|||
|
|||
// Fetching views and likes
|
|||
const viewsQuery = `SELECT COUNT(*) AS view_count FROM history WHERE video = $1`; |
|||
const viewsResult = await client.query(viewsQuery, [video]); |
|||
videoDetails.views = viewsResult.rows[0].view_count; |
|||
|
|||
// GET CREATOR
|
|||
const creatorQuery = `SELECT c.id, c.name, c.owner FROM channels c JOIN videos v ON c.id = v.channel WHERE v.id = $1`; |
|||
const creatorResult = await client.query(creatorQuery, [video]); |
|||
videoDetails.creator = creatorResult.rows[0]; |
|||
|
|||
// GET CREATOR PROFILE PICTURE
|
|||
const profilePictureQuery = `SELECT picture FROM users WHERE id = $1`; |
|||
const profilePictureResult = await client.query(profilePictureQuery, [videoDetails.creator.owner]); |
|||
videoDetails.creator.profile_picture = profilePictureResult.rows[0].picture; |
|||
|
|||
videos.push(videoDetails); |
|||
|
|||
} |
|||
|
|||
|
|||
|
|||
return res.status(200).json(videos); |
|||
|
|||
} |
|||
|
|||
|
|||
} catch (error) { |
|||
console.error("Error in search controller:", error); |
|||
res.status(500).json({ message: "Internal server error" }); |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
import { Router } from 'express'; |
|||
import {search} from "../controllers/search.controller.js"; |
|||
|
|||
const router = Router(); |
|||
|
|||
router.get('/', search) |
|||
|
|||
export default router; |
|||
|
After Width: | Height: | Size: 441 B |
@ -0,0 +1,142 @@ |
|||
import Navbar from "../components/Navbar.jsx"; |
|||
import {useState} from "react"; |
|||
|
|||
|
|||
export default function Account() { |
|||
|
|||
let user = JSON.parse(localStorage.getItem("user")) || {}; |
|||
|
|||
const [username, setUsername] = useState(user.username || ""); |
|||
const [email, setEmail] = useState(user.email || ""); |
|||
const [password, setPassword] = useState(""); |
|||
const [confirmPassword, setConfirmPassword] = useState(""); |
|||
const [isPictureEditActive, setIsPictureEditActive] = useState(false); |
|||
|
|||
const [userChannel, setUserChannel] = useState(null); |
|||
|
|||
const fetchUserChannel = async () => { |
|||
try { |
|||
const response = await fetch(`/api/channels/${user.id}`); |
|||
if (!response.ok) { |
|||
throw new Error("Failed to fetch user data"); |
|||
} |
|||
const data = await response.json(); |
|||
setUserChannel(data); |
|||
} catch (error) { |
|||
console.error("Error fetching user channel:", error); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
|
|||
const [editMode, setEditMode] = useState(false); |
|||
|
|||
const nonEditModeClasses = "text-2xl font-bold text-white p-2 focus:text-white focus:outline-none w-full font-montserrat"; |
|||
const editModeClasses = nonEditModeClasses + " glassmorphism"; |
|||
|
|||
return ( |
|||
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> |
|||
<Navbar/> |
|||
|
|||
<main className="px-36 pt-[118px]"> |
|||
{/* Left side */} |
|||
|
|||
{/* Profile / Edit profile */} |
|||
<form className="glassmorphism w-1/3 p-10"> |
|||
<div className="relative w-1/3 aspect-square overflow-hidden mb-3 mx-auto" onMouseEnter={() => setIsPictureEditActive(true)} onMouseLeave={() => setIsPictureEditActive(false)} > |
|||
<label htmlFor="image"> |
|||
<img |
|||
src={user.picture} |
|||
className="w-full aspect-square rounded-full object-cover" |
|||
/> |
|||
<div className={`absolute w-full h-full bg-[#000000EF] flex items-center justify-center top-0 left-0 rounded-full ${(isPictureEditActive && editMode) ? "opacity-100 cursor-pointer" : "opacity-0 cursor-default"} ` } > |
|||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" className="fill-white" viewBox="0 0 24 24"> |
|||
<path d="M19.045 7.401c.378-.378.586-.88.586-1.414s-.208-1.036-.586-1.414l-1.586-1.586c-.378-.378-.88-.586-1.414-.586s-1.036.208-1.413.585L4 13.585V18h4.413L19.045 7.401zm-3-3 1.587 1.585-1.59 1.584-1.586-1.585 1.589-1.584zM6 16v-1.585l7.04-7.018 1.586 1.586L7.587 16H6zm-2 4h16v2H4z"></path> |
|||
</svg> |
|||
</div> |
|||
</label> |
|||
<input type="file" accept="image/*" id="image" className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" disabled={!editMode}/> |
|||
</div> |
|||
<label htmlFor="name" className="text-2xl text-white mb-1 block font-montserrat"> |
|||
Nom d'utilisateur |
|||
</label> |
|||
<input |
|||
type="text" |
|||
id="name" |
|||
value={username} |
|||
className={(editMode ? editModeClasses : nonEditModeClasses)} |
|||
onChange={(e) => setUsername(e.target.value)} |
|||
placeholder="Nom d'utilisateur" |
|||
disabled={!editMode} |
|||
/> |
|||
|
|||
<label htmlFor="email" className="text-2xl text-white mb-1 mt-4 block font-montserrat"> |
|||
Adresse e-mail |
|||
</label> |
|||
<input |
|||
type="email" |
|||
id="email" |
|||
value={email} |
|||
className={(editMode ? editModeClasses : nonEditModeClasses)} |
|||
onChange={(e) => setEmail(e.target.value)} |
|||
placeholder="Adresse mail" |
|||
disabled={!editMode} |
|||
/> |
|||
|
|||
{ editMode && ( |
|||
<> |
|||
<label htmlFor="password" className="text-2xl text-white mb-1 mt-4 block font-montserrat"> |
|||
Mot de passe |
|||
</label> |
|||
<input |
|||
type="password" |
|||
id="password" |
|||
value={password} |
|||
className={(editMode ? editModeClasses : nonEditModeClasses)} |
|||
onChange={(e) => setPassword(e.target.value)} |
|||
placeholder="**************" |
|||
disabled={!editMode} |
|||
/> |
|||
|
|||
<label htmlFor="confirm-password" className="text-2xl text-white mb-1 mt-4 block font-montserrat"> |
|||
Confirmer le mot de passe |
|||
</label> |
|||
<input |
|||
type="password" |
|||
id="confirm-password" |
|||
value={confirmPassword} |
|||
className={(editMode ? editModeClasses : nonEditModeClasses)} |
|||
onChange={(e) => setConfirmPassword(e.target.value)} |
|||
placeholder="" |
|||
disabled={!editMode} |
|||
/> |
|||
</> |
|||
) |
|||
|
|||
} |
|||
|
|||
<div className="flex justify-center mt-5"> |
|||
<button |
|||
type="button" |
|||
className="bg-primary p-3 rounded-sm text-white font-montserrat text-2xl font-black cursor-pointer" |
|||
onClick={() => setEditMode(!editMode)} |
|||
> |
|||
{editMode ? "Enregistrer" : "Modifier le profil"} |
|||
</button> |
|||
</div> |
|||
</form> |
|||
|
|||
{ /* Right side */} |
|||
|
|||
{/* Channel */} |
|||
|
|||
{/* Playlists */} |
|||
|
|||
{/* History */} |
|||
|
|||
</main> |
|||
|
|||
</div> |
|||
) |
|||
|
|||
} |
|||
Loading…
Reference in new issue