diff --git a/backend/app/controllers/user.controller.js b/backend/app/controllers/user.controller.js index 56f2708..13eab1d 100644 --- a/backend/app/controllers/user.controller.js +++ b/backend/app/controllers/user.controller.js @@ -446,4 +446,24 @@ export async function isSubscribed(req, res) { client.end(); return res.status(200).json({subscribed: false}); } +} + +export async function searchByUsername(req, res) { + const username = req.query.username; + const client = await getClient(); + const logger = req.body.logger; + logger.action("try to search user by username " + username); + + const query = `SELECT * FROM users WHERE username ILIKE $1`; + const result = await client.query(query, [`%${username}%`]); + + if (result.rows.length === 0) { + logger.write("no user found with username " + username, 404); + client.end(); + return res.status(404).json({error: "User Not Found"}); + } + + logger.write("successfully found user with username " + username, 200); + client.end(); + res.status(200).json(result.rows); } \ No newline at end of file diff --git a/backend/app/routes/user.route.js b/backend/app/routes/user.route.js index bb2e095..e405495 100644 --- a/backend/app/routes/user.route.js +++ b/backend/app/routes/user.route.js @@ -8,7 +8,8 @@ import { deleteUser, getChannel, getHistory, isSubscribed, - verifyEmail + verifyEmail, + searchByUsername } from "../controllers/user.controller.js"; import { UserRegister, @@ -58,4 +59,7 @@ router.get("/:id/channel/subscribed", [addLogger, isTokenValid, User.id, Channel // VERIFY EMAIL router.post("/verify-email", [addLogger, validator], verifyEmail); +// SEARCH BY USERNAME +router.get("/search", [addLogger, isTokenValid], searchByUsername); + export default router; \ No newline at end of file diff --git a/backend/logs/access.log b/backend/logs/access.log index c893ed1..f3e3a42 100644 --- a/backend/logs/access.log +++ b/backend/logs/access.log @@ -7427,3 +7427,41 @@ [2025-08-17 10:06:21.133] [undefined] GET(/:id/history): try to retrieve history of user 2 [2025-08-17 10:06:21.138] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404 [2025-08-17 10:06:21.148] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-17 10:24:55.268] [undefined] POST(/): try to create new channel with owner 2 and name astria +[2025-08-17 10:24:55.271] [undefined] POST(/): Successfully created new channel with name astria with status 200 +[2025-08-17 10:24:55.288] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:24:55.292] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:24:56.366] [undefined] GET(/:id): try to get channel with id 1 +[2025-08-17 10:24:56.380] [undefined] GET(/:id): Successfully get channel with id 1 with status 200 +[2025-08-17 10:24:56.386] [undefined] GET(/:id/stats): try to get stats +[2025-08-17 10:24:56.395] [undefined] GET(/:id/stats): Successfully get stats with status 200 +[2025-08-17 10:25:00.825] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:25:00.830] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:28:34.803] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:28:34.807] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:29:51.209] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:29:51.214] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:32:59.876] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:32:59.880] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:33:09.144] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:33:09.148] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:43:59.502] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:43:59.507] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:45:08.860] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:45:08.863] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:45:12.454] [undefined] GET(/:id): failed due to invalid values with status 400 +[2025-08-17 10:46:31.555] [undefined] GET(/:id): failed due to invalid values with status 400 +[2025-08-17 10:53:32.094] [undefined] POST(/): Invalid token with status 401 +[2025-08-17 10:53:43.691] [undefined] POST(/): Invalid token with status 401 +[2025-08-17 10:54:16.538] [undefined] POST(/): Invalid token with status 401 +[2025-08-17 10:55:19.631] [undefined] GET(/:id/channel): try to retrieve channel of user 2 +[2025-08-17 10:55:19.636] [undefined] GET(/:id/channel): successfully retrieved channel of user 2 with status 200 +[2025-08-17 10:55:19.640] [undefined] GET(/:id/history): try to retrieve history of user 2 +[2025-08-17 10:55:19.645] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404 +[2025-08-17 10:55:19.656] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-17 10:55:25.476] [undefined] POST(/): Playlist created with id 3 with status 200 +[2025-08-17 10:55:25.497] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200 +[2025-08-17 10:55:57.559] [undefined] GET(/:id): Invalid token with status 401 +[2025-08-17 10:59:30.938] [undefined] GET(/:id): Invalid token with status 401 +[2025-08-17 11:04:33.569] [undefined] GET(/:id): Invalid token with status 401 +[2025-08-17 11:07:46.550] [undefined] POST(/): Invalid token with status 401 diff --git a/frontend/src/components/UserCard.jsx b/frontend/src/components/UserCard.jsx new file mode 100644 index 0000000..b64e7fe --- /dev/null +++ b/frontend/src/components/UserCard.jsx @@ -0,0 +1,14 @@ + + +export default function UserCard({user, doShowControls}) { + return ( +
+ {user.username} + {doShowControls && ( + + )} +
+ ); +} \ No newline at end of file diff --git a/frontend/src/pages/AddVideo.jsx b/frontend/src/pages/AddVideo.jsx index 560b9ec..0fa8132 100644 --- a/frontend/src/pages/AddVideo.jsx +++ b/frontend/src/pages/AddVideo.jsx @@ -1,8 +1,9 @@ import Navbar from "../components/Navbar.jsx"; import { useEffect, useState } from "react"; import Tag from "../components/Tag.jsx"; -import { getChannel } from "../services/user.service.js"; +import { getChannel, searchByUsername } from "../services/user.service.js"; import { uploadVideo, uploadThumbnail, uploadTags } from "../services/video.service.js"; +import { on } from "node:events"; export default function AddVideo() { @@ -14,10 +15,13 @@ export default function AddVideo() { const [videoTitle, setVideoTitle] = useState(""); const [videoDescription, setVideoDescription] = useState(""); const [videoTags, setVideoTags] = useState([]); - const [visibility, setVisibility] = useState("public"); + const [visibility, setVisibility] = useState("private"); const [videoThumbnail, setVideoThumbnail] = useState(null); const [videoFile, setVideoFile] = useState(null); const [channel, setChannel] = useState(null); + const [searchUser, setSearchUser] = useState(""); + const [authorizedUsers, setAuthorizedUsers] = useState([]); + const [searchResults, setSearchResults] = useState([]); const [alerts, setAlerts] = useState([]); useEffect(() => { @@ -102,6 +106,22 @@ export default function AddVideo() { setAlerts(alerts.filter(alert => alert !== alertToRemove)); }; + const onUserSearch = (e) => { + const searchUser = e.target.value; + if (searchUser.trim() !== "") { + // Call the API to search for users + searchByUsername(searchUser, token, addAlert) + .then((results) => { + setSearchResults(results); + }) + .catch((error) => { + addAlert('error', 'Erreur lors de la recherche d\'utilisateurs.'); + }); + } else { + setSearchResults([]); + } + } + return (
@@ -166,6 +186,65 @@ export default function AddVideo() { + + + + + + + + + { + visibility == "private" && ( +
+ +
+ setSearchUser(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter' && searchUser.trim() !== "") { + onUserSearch(e); + } + }} + /> +
+ +
+
+
+ {authorizedUsers.map((user, index) => ( +
+ {user} + +
+ ))} +
+
+ ) + } + + + + + + + + + + + +