From 91d4a9edb91067e30cb64422759ec2b9c1ac802c Mon Sep 17 00:00:00 2001 From: Shishir Mahato Date: Mon, 2 Feb 2026 17:45:25 +0530 Subject: [PATCH 1/4] fix: clean HTML structure and resolve Prettier CI failures --- .eslintrc.json | 5 +- CODE_OF_CONDUCT.md | 28 +- CONTRIBUTING.md | 70 ++--- README.md | 1 + index.html | 395 ++++++++++++++++------------- public/offline.html | 25 +- public/service-worker.js | 147 +++++------ server.js | 66 ++--- src/controllers/AuthControllers.js | 207 +++++++-------- src/css/style.css | 33 ++- src/js/main.js | 13 +- src/middleware/auth.js | 65 ++--- src/models/User.js | 76 +++--- streams.json | 2 +- vite.config.js | 3 +- 15 files changed, 600 insertions(+), 536 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 6b3244f..55ac219 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,10 +3,7 @@ "browser": true, "es2021": true }, - "extends": [ - "eslint:recommended", - "prettier" - ], + "extends": ["eslint:recommended", "prettier"], "parserOptions": { "ecmaVersion": 12, "sourceType": "module" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 99943c8..b79e969 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,3 @@ - # Contributor Covenant Code of Conduct ## Our Pledge @@ -18,23 +17,23 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -* Focusing on what is best not just for us as individuals, but for the +- Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: -* The use of sexualized language or imagery, and sexual attention or +- The use of sexualized language or imagery, and sexual attention or advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities @@ -107,7 +106,7 @@ Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an +standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within @@ -119,11 +118,11 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. -Community Impact Guidelines were inspired by +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org @@ -131,4 +130,3 @@ at [https://www.contributor-covenant.org/translations][translations]. [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b0b994..38d4a8c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,8 +3,8 @@ This project welcomes contributors to help improve its codebase by identifying shortcomings in the project, highlighting bugs, and issues in the documentation. -This is not a 'for hire' project. It's open source, so contribution to this project -is based on developer community improvements. This streaming platform was created specifically +This is not a 'for hire' project. It's open source, so contribution to this project +is based on developer community improvements. This streaming platform was created specifically by community developers for developers, hobbyists alike. This is a PWA. @@ -15,25 +15,25 @@ Anyone with basic programming skills down contribute immensely to this project. Areas requiring contribution includes: -- **Error checks**: Identify any mispellings or errors in documentation +- **Error checks**: Identify any mispellings or errors in documentation -- **Documentation**: Possess good documentation skills: Good spelling, punctuation, knowledge of Markdown and grammar checker tool, and you believe you can help elevate this projects' documentation even better? +- **Documentation**: Possess good documentation skills: Good spelling, punctuation, knowledge of Markdown and grammar checker tool, and you believe you can help elevate this projects' documentation even better? -- **Code contribution**: Identify a bug in this app or thought of a new feature that could plunge this app to a new dimension. +- **Code contribution**: Identify a bug in this app or thought of a new feature that could plunge this app to a new dimension. -- **Tests**: Contribute CI, CD, Docker, Unit tests, E2E tests, Integration tests to -help us ensure PRs don't introduce breaking changes, etc. +- **Tests**: Contribute CI, CD, Docker, Unit tests, E2E tests, Integration tests to + help us ensure PRs don't introduce breaking changes, etc. -- **Browser Testing**: Ideally this should work with no or minimal browser console errors, warnings, alerts. -If you encounter any specific warnings, errors, alerts. -Ideally this should work across mobile, desktop and many different devices Windows, Mac, Linux and different processor architectures as -well as different browsers. +- **Browser Testing**: Ideally this should work with no or minimal browser console errors, warnings, alerts. + If you encounter any specific warnings, errors, alerts. + Ideally this should work across mobile, desktop and many different devices Windows, Mac, Linux and different processor architectures as + well as different browsers. Please review your browser's console during development. 'console.log' everything necessary to ensure everything is working. -Add error messages where necessary +Add error messages where necessary ## How to contribute @@ -43,7 +43,7 @@ These next subheadings will get you started on making your contributions to this If you are not sure what to work on, review the issues list first. There are also TODO's listed in the included [TODO.md](./TODO.md) file. -Please '//TODO' mark incomplete areas and add them to the TODO.md file. +Please '//TODO' mark incomplete areas and add them to the TODO.md file. @gbowne1 and other project maintainers will assign users to issues on a first come, first serve basis. If you would like to work on an issue, feel free to indicate by tagging the mentioned persons. @@ -60,9 +60,9 @@ If none has been created, Click on the green [New Issue] button. Describe your issue as well as you can: -- Include code snippits where the issue lies. -- Include screenshots (optional). -- Include a short video (optional). +- Include code snippits where the issue lies. +- Include screenshots (optional). +- Include a short video (optional). If the issue appears to be a browser, operating system, device specific issue, let us know what those are. @@ -78,11 +78,11 @@ tool and editor/IDE agnostic so you can use whatever editor or IDE or you like. The [DEVSETUP](/docs/DEVSETUP.md) file contains instructions for developers on how to: -- Clone or Fork the project, -- Set up the project in their IDE or editor, -- Set up their environment to work in the project, including any environment variables, yarn/npm/pnpm package(s), editor extensions or plugins needed, -- Setting up the connection to the database, -- Setting up the local development database, MongoDB. +- Clone or Fork the project, +- Set up the project in their IDE or editor, +- Set up their environment to work in the project, including any environment variables, yarn/npm/pnpm package(s), editor extensions or plugins needed, +- Setting up the connection to the database, +- Setting up the local development database, MongoDB. ## Prerequisites to use Database for programmers @@ -92,7 +92,7 @@ Also, use accessibility (A11y) with themes and styles paying attention to focus styling, contrast and keyboard accesibility. Lastly, the browser gives us things like localStorage, IndexedDB, Session Storage, -Cookies and Cache Storage. Make secure usage of all of these +Cookies and Cache Storage. Make secure usage of all of these ## Pull Request @@ -104,14 +104,14 @@ Assignees, Reviewers, Labels, Projects, Milestone(s) and Development before you Also, write a brief description of what you fixed. Keep in mind that Blank issues and PR's without a description of the changes you made may not get merged. -- Link an issue to Development that the PR will close -- Make sure that you tag a reviewer i.e. @gbowne1 -- Pick appropriate labels from Labels -- Make sure you are the assignee to the PR. -- Milestone, choose Frontend or Backend (more may come later on) +- Link an issue to Development that the PR will close +- Make sure that you tag a reviewer i.e. @gbowne1 +- Pick appropriate labels from Labels +- Make sure you are the assignee to the PR. +- Milestone, choose Frontend or Backend (more may come later on) Please provide a short video, copy of log(s), passing tests, -screenshots of working changes. This helps with reviewers knowing +screenshots of working changes. This helps with reviewers knowing what to expect with your changes and contributions. If you manage to contribute a minimum of 3 accepted and merged PRs, you can request to be a contributor/maintainer/collsborator. @@ -137,7 +137,7 @@ working with our code base. ## Tech Stack -This project was bootstrapped with these libraries, modules, packages +This project was bootstrapped with these libraries, modules, packages All core components, modules, etc. are built with JavaScript. @@ -145,11 +145,11 @@ All core components, modules, etc. are built with JavaScript. Our branches follow GitFlow / GitHub Flow as a general rule. -- [ main ] main working branch -- [ master ] Permanent // Archive branch -- [ test ] untested code -- Feature Branch # of feature - {feature} -- [bugfix - { fixed bug }] -- [hotfix - { fix }] +- [ main ] main working branch +- [ master ] Permanent // Archive branch +- [ test ] untested code +- Feature Branch # of feature - {feature} +- [bugfix - { fixed bug }] +- [hotfix - { fix }] Use a test branch to commit/push code that you believe should work but is not completely tested. diff --git a/README.md b/README.md index 4881424..1f87b90 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # codestream + This is the code for a live streaming platform for programmers currently called DevStream but naming things is hard so this name is a placeholder. It's mostly a WIP and a Proof of Concept at the moment. diff --git a/index.html b/index.html index be88860..43e5b27 100644 --- a/index.html +++ b/index.html @@ -1,15 +1,15 @@ - + - - - - - - - - + + + + + - - - DevStream | Live Coding & Game Development - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -

DevStream

-
- - -
-
- - - - - -
- - -
- - -
-

Live Streams

- - -
- + /> + + + DevStream | Live Coding & Game Development + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +

DevStream

+
+ + +
+
+ + + + + +
+ + +
+ +
+

Live Streams

+ + +
+ +
-
-
- - -
-

Popular Tags

-
- JavaScript - Python - Rust - English - Linux -
-
- -
- - - - - - - - - + +
+
+
+

Loading live streams...

+
+
+ + + +
+

Popular Tags

+
+ JavaScript + Python + Rust + English + Linux +
+
+ + + + + + + + + + diff --git a/public/offline.html b/public/offline.html index ab112fb..17832f4 100644 --- a/public/offline.html +++ b/public/offline.html @@ -1,16 +1,19 @@ - + - - - + + + Offline - - - + + +
-

You are Offline

-

It seems you are not connected to the internet. Please check your connection and try again.

- Go to Home +

You are Offline

+

+ It seems you are not connected to the internet. Please check your + connection and try again. +

+ Go to Home
- + diff --git a/public/service-worker.js b/public/service-worker.js index 003f0b5..02b3e09 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,89 +1,94 @@ // service-worker.js -const CACHE_NAME = "devstream-v1"; +const CACHE_NAME = 'devstream-v1'; // List of resources to cache const CACHE_RESOURCES = [ - "/", - "/index.html", - "/src/css/style.css", - "/src/js/main.js", - "/manifest.json", - "/src/assets/images/preview-placeholder.jpg", - "/src/assets/icons/icon-192x192.png", - "/src/assets/icons/icon-512x512.png", - "/src/assets/icons/maskable-icon-192x192.png", - "/src/assets/icons/maskable-icon-512x512.png", - "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css", - "https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css", - "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js", - "https://fonts.googleapis.com/css2?family=Roboto:wght@400&display=swap", - "https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Mu4mxK.woff2" + '/', + '/index.html', + '/src/css/style.css', + '/src/js/main.js', + '/manifest.json', + '/src/assets/images/preview-placeholder.jpg', + '/src/assets/icons/icon-192x192.png', + '/src/assets/icons/icon-512x512.png', + '/src/assets/icons/maskable-icon-192x192.png', + '/src/assets/icons/maskable-icon-512x512.png', + 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css', + 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css', + 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js', + 'https://fonts.googleapis.com/css2?family=Roboto:wght@400&display=swap', + 'https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Mu4mxK.woff2', ]; // Install event handler -self.addEventListener("install", (event) => { - event.waitUntil( - caches.open(CACHE_NAME).then((cache) => { - console.log("Installing service worker..."); - return cache.addAll(CACHE_RESOURCES); - }).catch(error => { - console.error("Error during cache installation:", error); - }) - ); +self.addEventListener('install', (event) => { + event.waitUntil( + caches + .open(CACHE_NAME) + .then((cache) => { + console.log('Installing service worker...'); + return cache.addAll(CACHE_RESOURCES); + }) + .catch((error) => { + console.error('Error during cache installation:', error); + }) + ); }); // Activate event handler -self.addEventListener("activate", (event) => { - event.waitUntil( - caches.keys().then((cacheNames) => { - return Promise.all( - cacheNames.map((cacheName) => { - if (cacheName !== CACHE_NAME) { - console.log("Removing outdated cache:", cacheName); - return caches.delete(cacheName); - } - }) - ); +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames.map((cacheName) => { + if (cacheName !== CACHE_NAME) { + console.log('Removing outdated cache:', cacheName); + return caches.delete(cacheName); + } }) - ); + ); + }) + ); }); // Fetch event handler with cache-first strategy -self.addEventListener("fetch", (event) => { - event.respondWith( - caches.match(event.request).then((response) => { - if (response) { - // Update cache in background while serving cached response - event.waitUntil( - fetch(event.request).then((newResponse) => { - if (newResponse.ok) { - caches.open(CACHE_NAME).then((cache) => { - cache.put(event.request, newResponse.clone()); - }); - } - }).catch(error => { - console.error("Failed to update cache:", error); - }) - ); - return response; - } - - // Handle non-cached requests - return fetch(event.request).catch(error => { - console.error("Network request failed:", error); - - // Serve offline fallback for navigation requests - if (event.request.mode === 'navigate') { - return caches.match('/offline.html'); - } - }); - }) - ); +self.addEventListener('fetch', (event) => { + event.respondWith( + caches.match(event.request).then((response) => { + if (response) { + // Update cache in background while serving cached response + event.waitUntil( + fetch(event.request) + .then((newResponse) => { + if (newResponse.ok) { + caches.open(CACHE_NAME).then((cache) => { + cache.put(event.request, newResponse.clone()); + }); + } + }) + .catch((error) => { + console.error('Failed to update cache:', error); + }) + ); + return response; + } + + // Handle non-cached requests + return fetch(event.request).catch((error) => { + console.error('Network request failed:', error); + + // Serve offline fallback for navigation requests + if (event.request.mode === 'navigate') { + return caches.match('/offline.html'); + } + }); + }) + ); }); // Message event handler for debugging -self.addEventListener("message", (event) => { - if (event.data === "skipWaiting") { - self.skipWaiting(); - } +self.addEventListener('message', (event) => { + if (event.data === 'skipWaiting') { + self.skipWaiting(); + } }); diff --git a/server.js b/server.js index 79b3bb9..03aebf9 100644 --- a/server.js +++ b/server.js @@ -5,7 +5,7 @@ import mongoose from 'mongoose'; import fs from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; -import * as AuthControllers from './src/controllers/AuthControllers.js'; +import * as AuthControllers from './src/controllers/AuthControllers.js'; import { auth, authorizeRole } from './src/middleware/auth.js'; const app = express(); @@ -18,27 +18,25 @@ const __dirname = dirname(__filename); // Enable CORS so your frontend can communicate with this API // Middleware setup (must be before routes) -app.use(cors({ origin: 'http://localhost:3000' })); // SECURE CORS +app.use(cors({ origin: 'http://localhost:3000' })); // SECURE CORS app.use(express.json()); // DATABASE CONNECTION -mongoose.connect(MONGODB_URI) - .then(() => console.log('🟢 MongoDB connected successfully.')) - .catch(err => console.error('šŸ”“ MongoDB connection error:', err)); - - -// AUTH ROUTES -app.post('/api/auth/register', AuthControllers.register); -app.post('/api/auth/login', AuthControllers.login); +mongoose + .connect(MONGODB_URI) + .then(() => console.log('🟢 MongoDB connected successfully.')) + .catch((err) => console.error('šŸ”“ MongoDB connection error:', err)); +// AUTH ROUTES +app.post('/api/auth/register', AuthControllers.register); +app.post('/api/auth/login', AuthControllers.login); /** * @route GET /api/auth/me * @desc Gets the currently logged-in user's details (eg: username, role). * This endpoint demonstrates basic 'auth' middleware protection. */ -app.get('/api/auth/me', auth, AuthControllers.getUserDetails); - +app.get('/api/auth/me', auth, AuthControllers.getUserDetails); /** * @route GET /api/admin/dashboard @@ -46,8 +44,8 @@ app.get('/api/auth/me', auth, AuthControllers.getUserDetails); * This demonstrates Role-Based Access Control. */ app.get('/api/admin/dashboard', auth, authorizeRole(['admin']), (req, res) => { - // req.user is available here due to the 'auth' middleware - res.json({ message: `Access granted, Admin ID: ${req.user.id}.` }); + // req.user is available here due to the 'auth' middleware + res.json({ message: `Access granted, Admin ID: ${req.user.id}.` }); }); /** @@ -55,7 +53,7 @@ app.get('/api/admin/dashboard', auth, authorizeRole(['admin']), (req, res) => { * This fixes the "Cannot GET /" error by providing a landing page. */ app.get('/', (req, res) => { - res.send(` + res.send(`

šŸš€ DevStream API is Online

The server is running correctly.

@@ -69,23 +67,27 @@ app.get('/', (req, res) => { * Reads the mock data from streams.json and returns it as JSON. */ app.get('/api/streams', (req, res) => { - const dataPath = join(__dirname, 'streams.json'); - - fs.readFile(dataPath, 'utf8', (err, data) => { - if (err) { - console.error("Error reading streams.json:", err); - return res.status(500).json({ error: "Internal Server Error: Could not read data file." }); - } - try { - res.json(JSON.parse(data)); - } catch (parseErr) { - console.error("Error parsing JSON:", parseErr); - res.status(500).json({ error: "Internal Server Error: Invalid JSON format." }); - } - }); + const dataPath = join(__dirname, 'streams.json'); + + fs.readFile(dataPath, 'utf8', (err, data) => { + if (err) { + console.error('Error reading streams.json:', err); + return res + .status(500) + .json({ error: 'Internal Server Error: Could not read data file.' }); + } + try { + res.json(JSON.parse(data)); + } catch (parseErr) { + console.error('Error parsing JSON:', parseErr); + res + .status(500) + .json({ error: 'Internal Server Error: Invalid JSON format.' }); + } + }); }); app.listen(PORT, () => { - console.log(`\nāœ… Server successfully started!`); - console.log(`šŸ  Home: http://localhost:${PORT}`); -}); \ No newline at end of file + console.log(`\nāœ… Server successfully started!`); + console.log(`šŸ  Home: http://localhost:${PORT}`); +}); diff --git a/src/controllers/AuthControllers.js b/src/controllers/AuthControllers.js index b8bbb5c..e354f26 100644 --- a/src/controllers/AuthControllers.js +++ b/src/controllers/AuthControllers.js @@ -3,7 +3,7 @@ import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; const JWT_SECRET = process.env.JWT_SECRET; -const SALT_ROUNDS = 10; +const SALT_ROUNDS = 10; /** * @route POST /api/auth/register @@ -11,60 +11,63 @@ const SALT_ROUNDS = 10; */ export const register = async (req, res) => { - const { username, email, password } = req.body; - - if (!username || !email || !password) { - return res.status(400).json({ message: 'Please enter all fields.' }); + const { username, email, password } = req.body; + + if (!username || !email || !password) { + return res.status(400).json({ message: 'Please enter all fields.' }); + } + + try { + // 1. Check if user already exists + let user = await User.findOne({ email }); + if (user) { + return res + .status(400) + .json({ message: 'User with that email already exists.' }); } - try { - // 1. Check if user already exists - let user = await User.findOne({ email }); - if (user) { - return res.status(400).json({ message: 'User with that email already exists.' }); - } - - // 2. Hash Password - const salt = await bcrypt.genSalt(SALT_ROUNDS); - const passwordHashed = await bcrypt.hash(password, salt); - - // 3. Create new user instance - user = new User({ - username, - email, - password: passwordHashed, - role: 'user' - }); - - // 4. Save to DB - await user.save(); - - // 5. Create JWT - const payload = { - id: user.id, - role: user.role - }; - const token = jwt.sign(payload, JWT_SECRET, { expiresIn: '1d' }); - - // 6. Respond with token and user info - res.status(201).json({ - token, - user: { - id: user.id, - username: user.username, - email: user.email, - role: user.role - } - }); - - } catch (err) { - // Handle validation errors - if (err.code === 11000) { - return res.status(400).json({ message: 'Username or email already taken.' }); - } - console.error(err.message); - res.status(500).send('Server error during registration.'); + // 2. Hash Password + const salt = await bcrypt.genSalt(SALT_ROUNDS); + const passwordHashed = await bcrypt.hash(password, salt); + + // 3. Create new user instance + user = new User({ + username, + email, + password: passwordHashed, + role: 'user', + }); + + // 4. Save to DB + await user.save(); + + // 5. Create JWT + const payload = { + id: user.id, + role: user.role, + }; + const token = jwt.sign(payload, JWT_SECRET, { expiresIn: '1d' }); + + // 6. Respond with token and user info + res.status(201).json({ + token, + user: { + id: user.id, + username: user.username, + email: user.email, + role: user.role, + }, + }); + } catch (err) { + // Handle validation errors + if (err.code === 11000) { + return res + .status(400) + .json({ message: 'Username or email already taken.' }); } + console.error(err.message); + res.status(500).send('Server error during registration.'); + } }; /** @@ -72,47 +75,48 @@ export const register = async (req, res) => { * @desc Authenticate user and get token */ export const login = async (req, res) => { - const { email, password } = req.body; - - if (!email || !password) { - return res.status(400).json({ message: 'Please enter email and password.' }); + const { email, password } = req.body; + + if (!email || !password) { + return res + .status(400) + .json({ message: 'Please enter email and password.' }); + } + + try { + // 1. Find user by email + const user = await User.findOne({ email }); + if (!user) { + return res.status(400).json({ message: 'Invalid Credentials.' }); } - try { - // 1. Find user by email - const user = await User.findOne({ email }); - if (!user) { - return res.status(400).json({ message: 'Invalid Credentials.' }); - } - - // 2. Compare Password - const isMatch = await bcrypt.compare(password, user.password); - if (!isMatch) { - return res.status(444).json({ message: 'Invalid Credentials.' }); - } - - // 3. Create JWT - const payload = { - id: user.id, - role: user.role - }; - const token = jwt.sign(payload, JWT_SECRET, { expiresIn: '1d' }); - - // 4. Respond with token and user info - res.json({ - token, - user: { - id: user.id, - username: user.username, - email: user.email, - role: user.role - } - }); - - } catch (err) { - console.error(err.message); - res.status(500).send('Server error during login.'); + // 2. Compare Password + const isMatch = await bcrypt.compare(password, user.password); + if (!isMatch) { + return res.status(444).json({ message: 'Invalid Credentials.' }); } + + // 3. Create JWT + const payload = { + id: user.id, + role: user.role, + }; + const token = jwt.sign(payload, JWT_SECRET, { expiresIn: '1d' }); + + // 4. Respond with token and user info + res.json({ + token, + user: { + id: user.id, + username: user.username, + email: user.email, + role: user.role, + }, + }); + } catch (err) { + console.error(err.message); + res.status(500).send('Server error during login.'); + } }; /** @@ -120,16 +124,15 @@ export const login = async (req, res) => { * @desc Get user data (protected route) */ export const getUserDetails = async (req, res) => { - try { - const user = await User.findById(req.user.id).select('-password'); - if (!user) { - return res.status(404).json({ message: 'User not found.' }); - } - // Return the user data (without the password) - res.json(user); - - } catch (err) { - console.error(err.message); - res.status(500).send('Server error retrieving user data.'); + try { + const user = await User.findById(req.user.id).select('-password'); + if (!user) { + return res.status(404).json({ message: 'User not found.' }); } -}; \ No newline at end of file + // Return the user data (without the password) + res.json(user); + } catch (err) { + console.error(err.message); + res.status(500).send('Server error retrieving user data.'); + } +}; diff --git a/src/css/style.css b/src/css/style.css index 1db2bc2..ab231e1 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -1,14 +1,18 @@ @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400&display=swap'); /* Reset and base */ -*, *::before, *::after { +*, +*::before, +*::after { margin: 0; padding: 0; box-sizing: border-box; } :root { - --font-base: 'Roboto', 'OpenSans', 'Lato', 'Montserrat', 'Noto Sans', 'Oswald', 'Poppins', 'Raleway', system-ui, -apple-system, sans-serif; + --font-base: + 'Roboto', 'OpenSans', 'Lato', 'Montserrat', 'Noto Sans', 'Oswald', + 'Poppins', 'Raleway', system-ui, -apple-system, sans-serif; --primary: #4fc3f7; @@ -51,7 +55,9 @@ header { /* Navigation links within the header */ header .nav-link { - color: var(--color-header-text); /* Ensure nav links get the header text color */ + color: var( + --color-header-text + ); /* Ensure nav links get the header text color */ } /* Footer */ @@ -60,7 +66,6 @@ footer { color: var(--color-footer-text); } - /* Card Styles */ .card { background-color: var(--color-card-bg); @@ -72,11 +77,14 @@ footer { } /* Card texts and viewer counts */ -.card-text, .viewer-count, .card-title { +.card-text, +.viewer-count, +.card-title { color: var(--color-card-text); } -.card-text, .viewer-count { +.card-text, +.viewer-count { /* Use card text color to override bootstrap defaults */ color: var(--color-card-text); } @@ -94,13 +102,15 @@ body.dark .badge.bg-primary { #categoryDrawer { width: 280px; transform: translateX(-100%); - transition: transform 0.3s ease-in-out, visibility 0.3s ease-in-out, opacity 0.3s ease-in-out; + transition: + transform 0.3s ease-in-out, + visibility 0.3s ease-in-out, + opacity 0.3s ease-in-out; visibility: hidden; opacity: 0; z-index: 1050; } - #categoryDrawer.open { transform: translateX(0); visibility: visible; @@ -109,12 +119,12 @@ body.dark .badge.bg-primary { /* Dark mode for drawer when body is dark */ body.dark #categoryDrawer { - background-color: #222; /* Dark background for drawer */ - color: #eee; /* Light text for drawer */ + background-color: #222; /* Dark background for drawer */ + color: #eee; /* Light text for drawer */ } body.dark #categoryDrawer a { - color: #eee; /* Light text for drawer links */ + color: #eee; /* Light text for drawer links */ } /* Drawer Overlay */ @@ -157,7 +167,6 @@ body.dark #categoryDrawer a { transform: scale(0.98); } - .column-gap { -moz-column-gap: 1rem; /* For older versions of Firefox */ column-gap: 1rem; /* Adjust the value as needed */ diff --git a/src/js/main.js b/src/js/main.js index 31a09ae..8248d7f 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -112,7 +112,7 @@ document.addEventListener('DOMContentLoaded', () => { const filters = { category: categoryFilter?.value || '', subCategory: subCategoryFilter?.value || '', - mature: false + mature: false, }; const filtered = getFilteredStreams(allStreams, filters); @@ -206,7 +206,7 @@ document.addEventListener('DOMContentLoaded', () => { allStreams = data.map((s) => ({ ...s, img: s.thumbnail || s.img, - tags: s.tags || [s.category] + tags: s.tags || [s.category], })); renderStreams(allStreams); @@ -221,7 +221,7 @@ document.addEventListener('DOMContentLoaded', () => { categoryFilter?.addEventListener('change', applyFilters); subCategoryFilter?.addEventListener('change', applyFilters); - // šŸ”¹ SEARCH LOGIC (extracted for debounce) + // šŸ”¹ SEARCH LOGIC (extracted for debounce) function performSearch() { const term = searchInput.value.toLowerCase().trim(); clearSearch.classList.toggle('d-none', !term); @@ -248,7 +248,7 @@ document.addEventListener('DOMContentLoaded', () => { clearSearch.classList.add('d-none'); renderStreams(allStreams); }); - + /* ================= SEARCH ================= */ searchInput.addEventListener('input', () => { @@ -292,8 +292,9 @@ document.addEventListener('DOMContentLoaded', () => { const applyTheme = () => { document.body.classList.toggle('dark', isDark); - themeToggle.querySelector('i').className = - isDark ? 'fas fa-sun' : 'fas fa-moon'; + themeToggle.querySelector('i').className = isDark + ? 'fas fa-sun' + : 'fas fa-moon'; }; applyTheme(); diff --git a/src/middleware/auth.js b/src/middleware/auth.js index 9d6a898..84e4b1a 100644 --- a/src/middleware/auth.js +++ b/src/middleware/auth.js @@ -11,28 +11,29 @@ const JWT_SECRET = process.env.JWT_SECRET; */ export const auth = (req, res, next) => { - const authHeader = req.header('Authorization'); - - if (!authHeader) { - return res.status(401).json({ message: 'No token, authorization denied.' }); - } - - const token = authHeader.split(' ')[1]; - - if (!token) { - return res.status(401).json({ message: 'Token format invalid. Use Bearer scheme.' }); - } - - try { - const decoded = jwt.verify(token, JWT_SECRET); - - req.user = decoded; - - next(); - - } catch (err) { - res.status(401).json({ message: 'Token is not valid or has expired.' }); - } + const authHeader = req.header('Authorization'); + + if (!authHeader) { + return res.status(401).json({ message: 'No token, authorization denied.' }); + } + + const token = authHeader.split(' ')[1]; + + if (!token) { + return res + .status(401) + .json({ message: 'Token format invalid. Use Bearer scheme.' }); + } + + try { + const decoded = jwt.verify(token, JWT_SECRET); + + req.user = decoded; + + next(); + } catch (err) { + res.status(401).json({ message: 'Token is not valid or has expired.' }); + } }; /** @@ -41,11 +42,15 @@ export const auth = (req, res, next) => { * @param {string[]} requiredRoles Array of roles allowed (e.g., ['admin', 'moderator']) */ export const authorizeRole = (requiredRoles) => (req, res, next) => { - if (!req.user || !req.user.role) { - return res.status(500).json({ message: 'Authorization setup error: User role not found.' }); - } - if (!requiredRoles.includes(req.user.role)) { - return res.status(403).json({ message: 'Access denied. Insufficient permissions.' }); - } - next(); -}; \ No newline at end of file + if (!req.user || !req.user.role) { + return res + .status(500) + .json({ message: 'Authorization setup error: User role not found.' }); + } + if (!requiredRoles.includes(req.user.role)) { + return res + .status(403) + .json({ message: 'Access denied. Insufficient permissions.' }); + } + next(); +}; diff --git a/src/models/User.js b/src/models/User.js index e82ea2a..d0a90db 100644 --- a/src/models/User.js +++ b/src/models/User.js @@ -1,49 +1,49 @@ import mongoose from 'mongoose'; const UserSchema = new mongoose.Schema({ - username: { - type: String, - required: [true, 'Username is required'], - unique: true, - trim: true, - lowercase: true, - maxlength: 50 - }, - email: { - type: String, - required: [true, 'Email is required'], - unique: true, - trim: true, - lowercase: true, - match: [/.+\@.+\..+/, 'Please fill a valid email address'] - }, - // Store the hashed password - password: { - type: String, - required: [true, 'Password is required'], - minlength: 6 - }, - role: { - type: String, - enum: ['user', 'admin'], // For future authorization - default: 'user' - }, - createdAt: { - type: Date, - default: Date.now - } + username: { + type: String, + required: [true, 'Username is required'], + unique: true, + trim: true, + lowercase: true, + maxlength: 50, + }, + email: { + type: String, + required: [true, 'Email is required'], + unique: true, + trim: true, + lowercase: true, + match: [/.+\@.+\..+/, 'Please fill a valid email address'], + }, + // Store the hashed password + password: { + type: String, + required: [true, 'Password is required'], + minlength: 6, + }, + role: { + type: String, + enum: ['user', 'admin'], // For future authorization + default: 'user', + }, + createdAt: { + type: Date, + default: Date.now, + }, }); // Hide the password and version key when converting to JSON UserSchema.set('toJSON', { - transform: (document, returnedObject) => { - returnedObject.id = returnedObject._id.toString(); - delete returnedObject._id; - delete returnedObject.__v; - delete returnedObject.password; // Never send the hash to the client! - } + transform: (document, returnedObject) => { + returnedObject.id = returnedObject._id.toString(); + delete returnedObject._id; + delete returnedObject.__v; + delete returnedObject.password; // Never send the hash to the client! + }, }); const User = mongoose.model('User', UserSchema); -export default User; \ No newline at end of file +export default User; diff --git a/streams.json b/streams.json index a0d0015..3282531 100644 --- a/streams.json +++ b/streams.json @@ -39,4 +39,4 @@ "viewers": 450, "thumbnail": "https://placehold.co/600x400/000/fff?text=Leetcode" } -] \ No newline at end of file +] diff --git a/vite.config.js b/vite.config.js index 5076636..b7f49a4 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,7 +1,6 @@ import { defineConfig } from 'vite'; export default defineConfig({ - build: { outDir: '../dist', emptyOutDir: true, @@ -17,4 +16,4 @@ export default defineConfig({ }, }, }, -}); \ No newline at end of file +}); From c382233420d29883fabd99a5f98e18bede5b6036 Mon Sep 17 00:00:00 2001 From: Gregory Bowne Date: Mon, 16 Feb 2026 08:42:55 -0800 Subject: [PATCH 2/4] Fix typo in CACHE_NAME declaration Fixed typo in const of service worker --- public/service-worker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/service-worker.js b/public/service-worker.js index a349cde..c89c5bb 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,5 +1,5 @@ // service-worker.js -kipconst CACHE_NAME = 'devstream-v1'; +const CACHE_NAME = 'devstream-v1'; const CACHE_NAME = "devstream-v2"; // List of resources to cache From 5953fd4a445a326e4a052711434650dac8c87b75 Mon Sep 17 00:00:00 2001 From: Gregory Bowne Date: Mon, 16 Feb 2026 08:45:04 -0800 Subject: [PATCH 3/4] Fix doctype declaration in index.html --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 9befae4..579c7c8 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ - + From 08c9f4eca00827d52a1377f8629f264799cf7470 Mon Sep 17 00:00:00 2001 From: Shishir Mahato Date: Mon, 16 Feb 2026 23:59:42 +0530 Subject: [PATCH 4/4] Fix doctype declaration in offline.html --- public/offline.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/offline.html b/public/offline.html index 17832f4..b9f443a 100644 --- a/public/offline.html +++ b/public/offline.html @@ -1,4 +1,4 @@ - +