├── frontend ├── .eslintrc.json ├── app │ ├── favicon.ico │ ├── layout.js │ ├── home │ │ └── page.js │ ├── globals.css │ ├── page.js │ ├── profile │ │ └── page.js │ ├── login │ │ └── page.js │ └── register │ │ └── page.js ├── jsconfig.json ├── assets │ └── songphoto.png ├── next.config.js ├── postcss.config.js ├── public │ ├── abstract_figures.png │ ├── vercel.svg │ ├── next.svg │ └── groovewave.svg ├── components │ ├── Header.js │ ├── SongDisplay.js │ ├── navbar.js │ ├── Playlist.js │ └── Player.js ├── .gitignore ├── middleware.js ├── tailwind.config.js ├── package.json └── README.md ├── app.png ├── .gitignore ├── backend ├── utils │ └── catchAsync.js ├── Dockerfile ├── models │ ├── tokenModel.js │ ├── playlistModel.js │ ├── songModel.js │ └── userModel.js ├── package.json ├── routes │ ├── userRoutes.js │ ├── songRoutes.js │ └── playlistRoutes.js ├── controllers │ ├── testmiddleware.js │ ├── userController.js │ ├── songController.js │ └── playlistController.js ├── index.js ├── middleware.js └── README.md ├── CONTRIBUTING.md ├── README.md ├── CODE_OF_CONDUCT.md └── LICENSE /frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ojasaklechayt/Music-Streaming-App/HEAD/app.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore node_modules directory 2 | node_modules/ 3 | 4 | # Ignore .env files 5 | .env 6 | -------------------------------------------------------------------------------- /frontend/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ojasaklechayt/Music-Streaming-App/HEAD/frontend/app/favicon.ico -------------------------------------------------------------------------------- /frontend/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /frontend/assets/songphoto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ojasaklechayt/Music-Streaming-App/HEAD/frontend/assets/songphoto.png -------------------------------------------------------------------------------- /frontend/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/public/abstract_figures.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ojasaklechayt/Music-Streaming-App/HEAD/frontend/public/abstract_figures.png -------------------------------------------------------------------------------- /backend/utils/catchAsync.js: -------------------------------------------------------------------------------- 1 | module.exports = (fn) => { 2 | return (req, res, next) => { 3 | fn(req, res, next).catch(next); 4 | }; 5 | }; -------------------------------------------------------------------------------- /frontend/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Header = () => { 4 | return ( 5 |
6 |
7 |

Music Streaming App

8 |
9 |
10 | ); 11 | }; 12 | 13 | export default Header; 14 | -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Node.js runtime as the base image 2 | FROM node:14 3 | 4 | # Set the working directory inside the container 5 | WORKDIR /app/backend 6 | 7 | # Copy package.json and package-lock.json to the container 8 | COPY package*.json ./ 9 | 10 | # Install app dependencies 11 | RUN npm install 12 | 13 | # Copy the rest of the application code to the container 14 | COPY . . 15 | 16 | # Expose the port your app will run on 17 | EXPOSE 5000 18 | 19 | # Define the command to run your app 20 | CMD ["node", "index.js"] 21 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /frontend/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/models/tokenModel.js: -------------------------------------------------------------------------------- 1 | // Import the mongoose package for defining MongoDB schemas and models 2 | const mongoose = require('mongoose'); 3 | 4 | // Define a schema for the 'Token' collection in MongoDB 5 | const tokenSchema = mongoose.Schema({ 6 | // Define the 'token' field for the token document with a String type 7 | token: { 8 | type: String, 9 | required: true, // The 'token' field is required 10 | } 11 | }); 12 | 13 | // Create a 'Token' model using the defined schema 14 | const Token = mongoose.model('Token', tokenSchema); 15 | 16 | // Export the 'Token' model for use in other parts of the application 17 | module.exports = Token; 18 | -------------------------------------------------------------------------------- /frontend/middleware.js: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server' 2 | 3 | export function middleware(request) { 4 | const path = request.nextUrl.pathname 5 | 6 | 7 | const isPublic = path === '/login' || path === '/register' || path === '/' 8 | 9 | const token = request.cookies.get('token')?.value || '' 10 | 11 | if (isPublic && token) { 12 | return NextResponse.redirect(new URL('/home', request.nextUrl)) 13 | } 14 | if (!isPublic && !token) { 15 | return NextResponse.redirect(new URL('/', request.nextUrl)) 16 | } 17 | } 18 | 19 | 20 | export const config = { 21 | matcher: ['/', '/login', '/profile', '/register', '/home'], 22 | } -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './pages/**/*.{js,ts,jsx,tsx,mdx}', 5 | './components/**/*.{js,ts,jsx,tsx,mdx}', 6 | './app/**/*.{js,ts,jsx,tsx,mdx}', 7 | ], 8 | theme: { 9 | extend: { 10 | fontFamily: { 11 | poppins: ['Poppins', 'sans'], // Add Poppins font to your font-family 12 | }, 13 | backgroundImage: { 14 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 15 | 'gradient-conic': 16 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 17 | }, 18 | }, 19 | }, 20 | plugins: [], 21 | } 22 | -------------------------------------------------------------------------------- /frontend/app/layout.js: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import "./globals.css"; 3 | import { Inter } from "next/font/google"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata = { 8 | title: "Groovewave", 9 | description: 10 | "Our music streaming app redefines how you enjoy and discover music", 11 | }; 12 | 13 | export default function RootLayout({ children }) { 14 | return ( 15 | 16 | 17 | 21 | 22 | {children} 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "server": "nodemon index.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcrypt": "^5.1.1", 14 | "bcryptjs": "^2.4.3", 15 | "body-parser": "^1.20.2", 16 | "cookie-parser": "^1.4.6", 17 | "cors": "^2.8.5", 18 | "dotenv": "^16.3.1", 19 | "express": "^4.18.2", 20 | "fs": "^0.0.1-security", 21 | "jsonwebtoken": "^9.0.2", 22 | "mongodb": "^6.0.0", 23 | "mongoose": "^7.5.1", 24 | "multer": "^1.4.5-lts.1", 25 | "path": "^0.12.7" 26 | }, 27 | "devDependencies": { 28 | "nodemon": "^3.0.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@fortawesome/fontawesome-svg-core": "^6.4.2", 13 | "@fortawesome/free-solid-svg-icons": "^6.4.2", 14 | "@fortawesome/react-fontawesome": "^0.2.0", 15 | "axios": "^1.5.1", 16 | "cookies-next": "^4.0.0", 17 | "js-cookie": "^3.0.5", 18 | "next": "13.5.4", 19 | "react": "^18", 20 | "react-dom": "^18", 21 | "react-icons": "^4.11.0", 22 | "react-toastify": "^9.1.3", 23 | "react-transition-group": "^4.4.5" 24 | }, 25 | "devDependencies": { 26 | "autoprefixer": "^10", 27 | "eslint": "^8", 28 | "eslint-config-next": "13.5.4", 29 | "postcss": "^8", 30 | "tailwindcss": "^3" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/components/SongDisplay.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FaMusic } from 'react-icons/fa'; 3 | 4 | const SongDisplay = ({ songs }) => { 5 | // Assuming you have a state variable to track the currently playing song 6 | const currentlyPlayingSong = songs[0]; // Change this to the actual currently playing song 7 | 8 | return ( 9 |
10 |

11 | Now Playing 12 |

13 | {currentlyPlayingSong && ( 14 |
15 | {currentlyPlayingSong.title} - {currentlyPlayingSong.artist} 16 | {/* You can add more details about the currently playing song here */} 17 |
18 | )} 19 |
20 | ); 21 | }; 22 | 23 | export default SongDisplay; 24 | -------------------------------------------------------------------------------- /frontend/app/home/page.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import Header from '../../components/Header'; 3 | import Playlist from '../../components/Playlist'; 4 | import Player from '../../components/Player'; 5 | import SongDisplay from '../../components/SongDisplay'; 6 | 7 | const songs = [ 8 | { 9 | title: 'Song 1', 10 | artist: 'Artist 1', 11 | url: '/song1.mp3', // Provide actual audio file URL 12 | }, 13 | // Add more songs here 14 | ]; 15 | 16 | export default function Home() { 17 | return ( 18 |
19 | 20 | Music Streaming App 21 | 22 | 23 |
24 |
25 |
26 | 27 | 28 | 29 |
30 |
31 | 32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /backend/models/playlistModel.js: -------------------------------------------------------------------------------- 1 | // Import the mongoose package for defining MongoDB schemas and models 2 | const mongoose = require('mongoose'); 3 | 4 | // Define a schema for the 'Playlist' collection in MongoDB 5 | const playlistSchema = new mongoose.Schema({ 6 | // Define the 'name' field for the playlist with a String type 7 | name: { 8 | type: String, 9 | required: true, // The 'name' field is required 10 | }, 11 | // Define the 'creator' field as a reference to a User object (ObjectId) 12 | creator: { 13 | type: mongoose.Schema.Types.ObjectId, 14 | ref: 'User', // Reference to the 'User' model 15 | }, 16 | // Define the 'songs' field as an array of references to Song objects (ObjectIds) 17 | songs: [{ 18 | type: mongoose.Schema.Types.ObjectId, 19 | ref: 'Song', // Reference to the 'Song' model 20 | }], 21 | playlistPhoto: String 22 | }); 23 | 24 | // Create a 'Playlist' model using the defined schema 25 | const Playlist = mongoose.model('Playlist', playlistSchema); 26 | 27 | // Export the 'Playlist' model for use in other parts of the application 28 | module.exports = Playlist; 29 | -------------------------------------------------------------------------------- /frontend/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/routes/userRoutes.js: -------------------------------------------------------------------------------- 1 | // Import necessary modules and packages 2 | const express = require('express'); // Import Express.js for routing 3 | const router = express.Router(); // Create an instance of an Express router 4 | const userController = require('../controllers/userController'); // Import the userController module 5 | const { verifyjwt, logout, verifyToken } = require("../middleware"); // Import the verifyjwt middleware for authentication 6 | 7 | // Define routes for user-related actions, including registration and login 8 | // These routes do not require authentication 9 | 10 | // Route to handle user registration 11 | router.post('/register', userController.register); 12 | 13 | // Route to handle user login 14 | router.post('/login', userController.login); 15 | 16 | // Route to get user profile information 17 | 18 | // Apply the verifyjwt middleware to the routes below to ensure authentication is required 19 | router.get('/verify',verifyjwt, verifyToken); 20 | 21 | router.get('/profile',verifyjwt, userController.profile); 22 | 23 | router.post('/logout',verifyjwt, logout); 24 | // Define routes that require authentication 25 | 26 | // Route to update user profile information 27 | router.put('/profile/update',verifyjwt, userController.updateProfile); 28 | 29 | 30 | // Export the router with defined routes for use in other parts of the application 31 | module.exports = router; 32 | -------------------------------------------------------------------------------- /frontend/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | @import url('https://fonts.googleapis.com/css2?family=Inria+Serif&family=Koulen&family=Roboto:wght@300&display=swap'); 5 | @import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap'); 6 | 7 | :root { 8 | --foreground-rgb: 0, 0, 0; 9 | --background-start-rgb: 214, 219, 220; 10 | --background-end-rgb: 255, 255, 255; 11 | } 12 | 13 | @media (prefers-color-scheme: dark) { 14 | :root { 15 | --foreground-rgb: 255, 255, 255; 16 | --background-start-rgb: 0, 0, 0; 17 | --background-end-rgb: 0, 0, 0; 18 | } 19 | } 20 | 21 | body { 22 | color: rgb(var(--foreground-rgb)); 23 | background: linear-gradient(to bottom, 24 | transparent, 25 | rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb)); 26 | } 27 | 28 | @keyframes spin-slow { 29 | 0% { 30 | transform: rotate(0deg); 31 | } 32 | 33 | 100% { 34 | transform: rotate(360deg); 35 | } 36 | } 37 | 38 | .animate-spin-slow { 39 | animation: spin-slow 7s linear infinite; 40 | } 41 | 42 | .font-inria { 43 | font-family: 'Inria Serif', serif; 44 | } 45 | 46 | .font-koulen { 47 | font-family: 'Koulen', sans-serif; 48 | } 49 | 50 | .font-roboto { 51 | font-family: 'Roboto', sans-serif; 52 | font-weight: 300; 53 | } 54 | 55 | .font-poppins { 56 | font-family: 'Poppins', sans-serif; 57 | } -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /frontend/app/page.js: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import Image from 'next/image'; 3 | import Groovewave from '../public/groovewave.svg'; 4 | import { FaBars } from 'react-icons/fa'; 5 | import Disk from '../public/disk.svg'; 6 | import Navbar from '@/components/navbar'; 7 | 8 | export default function Home() { 9 | 10 | return ( 11 |
12 | 13 |
14 |
15 |

16 | Groovewave 17 |

18 |

19 | "Ride the GrooveWave: Your Sound, Your Vibe, Your Way!" 20 |

21 |

Are you ready to dive into a world of limitless music possibilities? GrooveWave is your passport to a universe of sounds, rhythms, and melodies. Whether you're a passionate music lover, an avid collector, or just looking for your next favorite song, GrooveWave has you covered

22 |
23 |
24 | Disk 25 |
26 |
27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /backend/models/songModel.js: -------------------------------------------------------------------------------- 1 | // Import the mongoose package for defining MongoDB schemas and models 2 | const mongoose = require('mongoose'); 3 | 4 | // Define a schema for the 'Song' collection in MongoDB 5 | const songSchema = new mongoose.Schema({ 6 | // Define the 'title' field for the song with a String type 7 | title: { 8 | type: String, 9 | required: true, // The 'title' field is required 10 | }, 11 | // Define the 'artist' field for the song with a String type 12 | artist: { 13 | type: String, 14 | required: true, // The 'artist' field is required 15 | }, 16 | // Define the 'genre' field for the song with a String type 17 | genre: { 18 | type: String, 19 | required: true, // The 'genre' field is required 20 | }, 21 | // Define the 'audioURL' field for the song with a String type 22 | audioURL: { 23 | type: String, 24 | }, 25 | // Define the 'owner' field as a reference to a User object (ObjectId) 26 | owner: { 27 | type: mongoose.Schema.Types.ObjectId, 28 | ref: 'User', // Reference to the 'User' model 29 | required: true, // The 'owner' field is required 30 | }, 31 | SongPhoto: String, 32 | // Define the 'audio' field to store the audio data as a Buffer 33 | audio: { 34 | data: Buffer, // Store audio data as a Buffer 35 | contentType: String, // Store the content type (e.g., 'audio/mp3') 36 | }, 37 | }); 38 | 39 | // Create a 'Song' model using the defined schema 40 | const Song = mongoose.model('Song', songSchema); 41 | 42 | // Export the 'Song' model for use in other parts of the application 43 | module.exports = Song; 44 | -------------------------------------------------------------------------------- /backend/routes/songRoutes.js: -------------------------------------------------------------------------------- 1 | // Import necessary modules and packages 2 | const express = require('express'); // Import Express.js for routing 3 | const router = express.Router(); // Create an instance of an Express router 4 | const songController = require('../controllers/songController'); // Import the songController module 5 | const { verifyjwt } = require("../middleware"); // Import the verifyjwt middleware for authentication 6 | const multer = require('multer'); 7 | 8 | const storage = multer.memoryStorage(); 9 | const upload = multer({ storage: storage }); 10 | 11 | // Apply the verifyjwt middleware to all routes defined in this router 12 | router.use(verifyjwt); 13 | 14 | // Define various routes and their corresponding controller functions 15 | // These routes are related to managing songs 16 | 17 | // Route to get all songs 18 | router.get('/', songController.getAllSongs); 19 | 20 | // Route to get songs by name 21 | router.get('/name', songController.getSongByName); 22 | 23 | // Route to get songs by artist 24 | router.get('/artist', songController.getSongByArtist); 25 | 26 | // Route to get songs by genre 27 | router.get('/genre', songController.getSongByGenre); 28 | 29 | // Route to upload a new song 30 | router.post('/', upload.single('audio'), songController.uploadSong); 31 | 32 | // Route to delete a song by user ID and song ID 33 | router.delete('/:userId/:songId', songController.deleteSong); 34 | 35 | // Route to edit a song by song ID 36 | router.put('/editsong/:songId', songController.editSong); 37 | 38 | // Route to get songs by user ID 39 | router.get('/:userId', songController.getSongByUser); 40 | 41 | // Export the router with defined routes for use in other parts of the application 42 | module.exports = router; 43 | -------------------------------------------------------------------------------- /backend/routes/playlistRoutes.js: -------------------------------------------------------------------------------- 1 | // Import necessary modules and packages 2 | const express = require('express'); // Import Express.js for routing 3 | const router = express.Router(); // Create an instance of an Express router 4 | const playlistController = require('../controllers/playlistController'); // Import the playlistController module 5 | const { verifyjwt } = require("../middleware"); // Import the verifyjwt middleware for authentication 6 | 7 | // Apply the verifyjwt middleware to all routes defined in this router 8 | router.use(verifyjwt); 9 | 10 | // Define various routes and their corresponding controller functions 11 | // These routes are related to managing playlists 12 | 13 | // Route to create a new playlist 14 | router.post("/", playlistController.createPlaylist); 15 | 16 | // Route to edit an existing playlist by its ID 17 | router.put("/:id", playlistController.editPlaylist); 18 | 19 | // Route to delete a playlist by its ID 20 | router.delete("/:id", playlistController.deletePlaylist); 21 | 22 | // Route to add a song to a playlist by playlist ID 23 | router.post("/:id/song", playlistController.addSongToPlaylist); 24 | 25 | // Route to remove a song from a playlist by playlist ID and song ID 26 | router.delete("/:id/delete/:songId", playlistController.removeSongFromPlaylist); 27 | 28 | // Route to find playlists by user ID 29 | router.get("/user/:userId", playlistController.findPlaylistsByUser); 30 | 31 | // Route to get all playlists 32 | router.get("/", playlistController.getAllPlaylists); 33 | 34 | // Route to get a playlist by its ID 35 | router.get("/:id", playlistController.getPlaylistbyId); 36 | 37 | // Route to get all songs from a playlist by playlist ID 38 | router.get("/:id/song", playlistController.getAllSongsFromPlaylist); 39 | 40 | // Export the router with defined routes for use in other parts of the application 41 | module.exports = router; 42 | -------------------------------------------------------------------------------- /backend/models/userModel.js: -------------------------------------------------------------------------------- 1 | // Import the mongoose package for defining MongoDB schemas and models 2 | const mongoose = require('mongoose'); 3 | 4 | // Define a schema for the 'User' collection in MongoDB 5 | const userSchema = new mongoose.Schema({ 6 | // Define the 'username' field for the user with a String type 7 | username: { 8 | type: String, 9 | required: true, // The 'username' field is required 10 | unique: true, // The 'username' field must be unique 11 | }, 12 | // Define the 'email' field for the user with a String type 13 | email: { 14 | type: String, 15 | required: true, // The 'email' field is required 16 | unique: true, // The 'email' field must be unique 17 | }, 18 | // Define the 'password' field for the user with a String type 19 | password: { 20 | type: String, 21 | required: true, // The 'password' field is required 22 | }, 23 | // Define the 'profilePicture' field for the user with an optional String type 24 | profilePicture: String, 25 | // Define the 'bio' field for the user with an optional String type 26 | bio: String, 27 | // Define the 'createdPlaylists' field as an array of references to Playlist objects (ObjectIds) 28 | createdPlaylists: [{ 29 | type: mongoose.Schema.Types.ObjectId, 30 | ref: 'Playlist', // Reference to the 'Playlist' model 31 | }], 32 | // Define the 'uploadedSongs' field as an array of references to Song objects (ObjectIds) 33 | uploadedSongs: [{ 34 | type: mongoose.Schema.Types.ObjectId, 35 | ref: 'Song', // Reference to the 'Song' model 36 | }] 37 | }); 38 | 39 | // Create a 'User' model using the defined schema 40 | const User = mongoose.model('User', userSchema); 41 | 42 | // Export the 'User' model for use in other parts of the application 43 | module.exports = User; 44 | -------------------------------------------------------------------------------- /backend/controllers/testmiddleware.js: -------------------------------------------------------------------------------- 1 | // import { getCookie } from "cookies-next"; 2 | // import { NextResponse } from "next/server"; 3 | 4 | // const authPath = ['/', '/login', '/register']; 5 | // const protectedRoutes = ['/home', '/profile']; 6 | 7 | // export async function middleware(request) { 8 | // const token = getCookie(request, 'token'); 9 | 10 | // console.log({ token }); 11 | // const response = await fetch("http://localhost:5000/users/verify", { 12 | // headers: new Headers({ 13 | // Authorization: "Bearer " + token, 14 | // }), 15 | // }); 16 | 17 | // // console.log(response); 18 | // const data = await response.json(); 19 | 20 | // // console.log(data); 21 | 22 | // return NextResponse.next(); 23 | // // if (!token) { 24 | // // if (authPath.includes(request.url)) { 25 | // // return NextResponse.next(); 26 | // // } else { 27 | // // return NextResponse.next(null, { status: 401 }); 28 | // // } 29 | // // } 30 | 31 | // // try { 32 | // // const backendURL = 'https://music-streaming-app.onrender.com/'; // Replace with the actual URL 33 | // // const response = await fetch(`${backendURL}/verify-token?token=${token}`); 34 | 35 | // // if (response.status === 200) { 36 | // // if (protectedRoutes.includes(request.url)) { 37 | // // return NextResponse.next(); 38 | // // } else { 39 | // // return NextResponse.next(null, { status: 401 }); 40 | // // } 41 | // // } else { 42 | // // if (authPath.includes(request.url)) { 43 | // // return NextResponse.next(); 44 | // // } else { 45 | // // return NextResponse.next(null, { status: 401 }); 46 | // // } 47 | // // } 48 | // // } catch (error) { 49 | // // console.error(error); 50 | // // return new NextResponse(null, { status: 500 }); 51 | // // } 52 | // } 53 | 54 | // export const config = { 55 | // matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"], 56 | // }; -------------------------------------------------------------------------------- /backend/index.js: -------------------------------------------------------------------------------- 1 | // Import necessary packages and modules 2 | const express = require("express"); // Express.js for building the web application 3 | const cors = require('cors'); // Middleware for handling Cross-Origin Resource Sharing (CORS) 4 | const mongoose = require('mongoose'); // MongoDB ORM for database interaction 5 | 6 | const userRoutes = require('./routes/userRoutes'); 7 | const songRoutes = require('./routes/songRoutes'); 8 | const playlistRoutes = require('./routes/playlistRoutes'); 9 | 10 | const port = process.env.PORT || 5000; // Port on which the server will run 11 | require('dotenv').config(); // Load environment variables from a .env file 12 | const app = express(); // Create an instance of the Express application 13 | 14 | 15 | 16 | // Enable CORS to allow cross-origin requests 17 | app.use(cors({ 18 | origin: true, // included origin as true 19 | credentials: true,//included credentials as true 20 | })); 21 | 22 | 23 | try { 24 | // Connect to MongoDB using the URL provided in the .env file 25 | mongoose.connect(process.env.MONGO_URL, { useNewUrlParser: true, useUnifiedTopology: true }); 26 | 27 | // Event handlers for MongoDB connection 28 | mongoose.connection.on('connected', () => { 29 | console.log('Connected to MongoDB'); 30 | }); 31 | 32 | mongoose.connection.on('error', (err) => { 33 | console.error('MongoDB connection error:', err); 34 | }); 35 | } catch (err) { 36 | // Handle any errors that occur during MongoDB connection 37 | console.error('An error occurred while connecting to MongoDB:', err); 38 | } 39 | 40 | // Import routes for different parts of the application 41 | 42 | 43 | // Use routes and apply CORS middleware to specific routes 44 | app.use(express.json({ limit: "50mb" })); 45 | app.use(express.urlencoded({ extended: true, limit: "50mb" })); 46 | app.use('/users', userRoutes); 47 | app.use('/songs', songRoutes); 48 | app.use('/playlists', playlistRoutes); 49 | 50 | // Define a default route that responds with a welcome message 51 | app.use('/', (req, res) => { 52 | res.send("Welcome to our music app"); 53 | }) 54 | 55 | // Start the server and listen on the specified port 56 | app.listen(port, () => { 57 | console.log(`Server is running on the port ${port}`) 58 | }); 59 | -------------------------------------------------------------------------------- /frontend/components/navbar.js: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { useState } from 'react' 3 | import Image from 'next/image'; 4 | import Groovewave from '../public/groovewave.svg'; 5 | import { FaBars } from 'react-icons/fa'; 6 | 7 | const Navbar = () => { 8 | const [isMenuOpen, setIsMenuOpen] = useState(false); 9 | 10 | const toggleMenu = () => { 11 | setIsMenuOpen(!isMenuOpen); 12 | }; 13 | return ( 14 |
15 | Navbar 16 |
17 | 18 |
19 |
20 | 30 |
31 | 32 | {isMenuOpen && ( 33 |
34 |
35 | 36 |
37 |
38 | )} 39 |
40 |
41 |
42 | ) 43 | } 44 | 45 | export default Navbar -------------------------------------------------------------------------------- /frontend/components/Playlist.js: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { useState } from 'react'; 3 | import { FaMusic } from 'react-icons/fa'; 4 | 5 | const playlists = [ 6 | { 7 | name: 'Chill Vibes', 8 | creator: 'User123', 9 | coverPhoto: 'https://i.pinimg.com/736x/20/6b/7c/206b7cc931be712a50fb7c5b612fa8bb.jpg', 10 | }, 11 | { 12 | name: 'Relaxing Tunes', 13 | creator: 'User456', 14 | coverPhoto: 'https://i.scdn.co/image/ab67616d0000b27385ee2cb57cb10c2295773b88', 15 | }, 16 | { 17 | name: 'Jazz Classics', 18 | creator: 'User789', 19 | coverPhoto: 'https://artwork-cdn.7static.com/static/img/sleeveart/00/313/192/0031319281_350.jpg', 20 | }, 21 | // Add more playlist objects as needed 22 | ]; 23 | 24 | const Playlist = (props) => { 25 | const [activeIndex, setActiveIndex] = useState(null); 26 | 27 | const handlePlaylistHover = (index) => { 28 | setActiveIndex(index); 29 | }; 30 | 31 | const handlePlaylistLeave = () => { 32 | setActiveIndex(null); 33 | }; 34 | 35 | return ( 36 |
37 | {props.type === "Popular" ?

Popular Playlists

:

My Playlists

} 38 |
39 | {playlists.map((playlist, index) => ( 40 |
handlePlaylistHover(index)} 45 | onMouseLeave={handlePlaylistLeave} 46 | > 47 | {playlist.name} 48 |
49 |

{playlist.name}

50 |

Creator: {playlist.creator}

51 |
52 |
53 | ))} 54 |
55 |
56 | ); 57 | }; 58 | 59 | export default Playlist; 60 | -------------------------------------------------------------------------------- /frontend/app/profile/page.js: -------------------------------------------------------------------------------- 1 | // pages/profile.js 2 | 'use client' 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 4 | import { faEdit } from '@fortawesome/free-solid-svg-icons'; 5 | import { useEffect, useState } from 'react'; 6 | 7 | // Dummy user data for testing 8 | const dummyUserData = { 9 | username: 'john_doe', 10 | email: 'johndoe@example.com', 11 | profilePicture: 'https://placekitten.com/200/200', // Replace with an actual image URL 12 | bio: 'Hello, I am John Doe. This is my profile.', 13 | createdPlaylists: ['Playlist 1', 'Playlist 2'], // Replace with actual playlist data 14 | uploadedSongs: ['Song 1', 'Song 2'], // Replace with actual song data 15 | }; 16 | 17 | export default function Profile() { 18 | const [user, setUser] = useState(dummyUserData); 19 | 20 | return ( 21 |
22 |
23 | {`${user.username}'s 28 |
29 |

{user.username}

30 | 31 | 32 | 33 |
34 |

{user.bio}

35 |
36 |

Email:

37 |

{user.email}

38 |
39 |
40 |

Created Playlists:

41 |
    42 | {user.createdPlaylists.map((playlist, index) => ( 43 |
  • {playlist}
  • 44 | ))} 45 |
46 |
47 |
48 |

Uploaded Songs:

49 |
    50 | {user.uploadedSongs.map((song, index) => ( 51 |
  • {song}
  • 52 | ))} 53 |
54 |
55 |
56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to SnapGrid 2 | 3 | Welcome to the Music Streaming App project! We appreciate your interest in contributing to our open-source project. Your contributions help us improve and enhance the project for all users. Here are some guidelines to get you started: 4 | 5 | ## Contribution Guidelines 6 | 7 | - Ensure your code follows the project's coding standards and conventions. 8 | - When you're submitting a PR for a UI-related issue, it would be really awesome if you add a screenshot of your change. It makes 9 | it very easy for the reviewers and you'll also get reviews quicker. 10 | - Keep your pull request concise and focused on a single issue or feature. 11 | - Before making any PR please create an Issue 12 | - If you find any upgradation or bug in the website, you can create your own issue or work on the previous issues. 13 | - When creating PR make sure you tag the required issue using # 14 | - Provide clear and informative commit messages. 15 | - Be open to feedback and engage in discussions to improve your contribution. 16 | - Respect the project maintainers and other contributors. 17 | - Please read our [Code of Conduct](./CODE_OF_CONDUCT.md). 18 | 19 | 20 | ## Getting Started 21 | 22 | Before you begin, please make sure you have Git installed on your computer. If you don't have it installed, you can download it from [Git Downloads](https://git-scm.com/downloads). 23 | 24 | ## Cloning the Repository 25 | 26 | To start contributing, you'll need to clone the project's Git repository to your local machine. Here are the steps to do so: 27 | 28 | 1. Open your terminal or command prompt. 29 | 30 | 2. Navigate to the directory where you want to clone the repository. 31 | ```bash 32 | cd /path/to/your/directory 33 | ``` 34 | 35 | 3.Clone the repository using the following command, replacing [repository URL] with the actual URL of the repository: 36 | ``` 37 | git clone https://github.com/ojasaklechayt/Music-Streaming-App.git 38 | ``` 39 | 40 | ## Configuration 41 | 42 | If this is your first time using Git on your computer, you may need to configure your Git user information. You can do this using the following commands, replacing [Your Name] and [youremail@example.com] with your actual name and email address: 43 | ``` 44 | git config --global user.name "Your Name" 45 | git config --global user.email "youremail@example.com" 46 | ``` 47 | 48 | ## Making Changes 49 | 50 | Now that you have the repository cloned and configured, you can start making changes to the project. Here are some basic Git commands to get you started: 51 | 52 | **git add .**: Stage changes for commit. 53 | **git commit -m "Your commit message"**: Commit staged changes with a message. 54 | **git push origin master**: Push changes to the remote repository. 55 | 56 | 57 | ## Submitting Your Changes 58 | After making your changes, you can submit your contributions by creating a pull request. Please refer to our Pull Request Guidelines for details on how to do this. 59 | 60 | 61 | Please make sure to follow our Contribution Guidelines and review the Code of Conduct to ensure a positive and inclusive community. 62 | **Happy Coding!!** -------------------------------------------------------------------------------- /backend/controllers/userController.js: -------------------------------------------------------------------------------- 1 | // Import necessary models and packages 2 | const User = require("../models/userModel"); 3 | const bcrypt = require("bcrypt"); 4 | const { generatejwt } = require("../middleware"); 5 | // Controller functions for user-related actions 6 | 7 | // Register a new user 8 | exports.register = async (req, res) => { 9 | try { 10 | const { username, email, password } = req.body; 11 | const saltRounds = 10; 12 | 13 | // Check if a user with the provided email already exists 14 | const existingUser = await User.findOne({ email }); 15 | if (existingUser) { 16 | return res.status(400).json({ message: "User already exists" }); 17 | } 18 | 19 | // Generate a salt for password hashing 20 | const salt = await bcrypt.genSalt(saltRounds); 21 | 22 | // Hash the provided password using the salt 23 | const encryptedPassword = await bcrypt.hash(password, salt); 24 | 25 | // Create a new user document with the provided data 26 | const newUser = new User({ 27 | username, 28 | email, 29 | password: encryptedPassword, 30 | }); 31 | 32 | // Save the new user to the database 33 | await newUser.save(); 34 | 35 | res 36 | .status(201) 37 | .json({ message: "User registered successfully", user: newUser }); 38 | } catch (error) { 39 | console.error("Error registering user: ", error); 40 | res.status(500).json({ message: "Registration failed" }); 41 | } 42 | }; 43 | 44 | // User login 45 | exports.login = async (req, res) => { 46 | try { 47 | const { email, password } = req.body; 48 | 49 | // Find a user with the provided email 50 | const user = await User.findOne({ email }); 51 | if (!user) { 52 | return res.status(401).json({ message: "User Not Found" }); 53 | } 54 | 55 | // Compare the provided password with the hashed password in the database 56 | const check = await bcrypt.compare(password, user.password); 57 | if (!check) { 58 | return res.status(401).json({ message: "Inavalid Password" }); 59 | } 60 | 61 | const token = await generatejwt(user._id); 62 | 63 | res.status(200).json({ status: "success", token }); 64 | // res.status(200).json({message:"Successfull"}); 65 | } catch (error) { 66 | console.error("Error Login User: ", error); 67 | res.status(500).json({ message: "Login failed" }); 68 | } 69 | }; 70 | 71 | // Fetch user profile 72 | exports.profile = async (req, res) => { 73 | try { 74 | const _Id = req.user._id; 75 | 76 | // Find the user by their ID 77 | const user = await User.findOne({ _id: _Id }); 78 | 79 | if (!user) { 80 | res.status(404).json({ message: "User Not Found!!" }); 81 | } 82 | 83 | res.status(200).json({ user }); 84 | } catch (error) { 85 | console.error("Error Fetching Profile: ", error); 86 | res.status(500).json({ message: "Profile Retrieval Failed" }); 87 | } 88 | }; 89 | 90 | // Update user profile 91 | exports.updateProfile = async (req, res) => { 92 | try { 93 | const { username, profilePicture, bio } = req.body; 94 | const userId = req.user._id; 95 | 96 | // Find the user by their ID 97 | const user = await User.findOne({ _id: userId }); 98 | 99 | if (!user) { 100 | res.status(404).json({ message: "User Not Found" }); 101 | } 102 | 103 | // Update user data if provided 104 | if (username) user.username = username; 105 | if (profilePicture) user.profilePicture = profilePicture; 106 | if (bio) user.bio = bio; 107 | 108 | // Save the updated user data 109 | await user.save(); 110 | 111 | res.status(200).json({ message: "Profile Updated Successfully", user }); 112 | } catch (error) { 113 | console.error("Error Updating Profile: ", error); 114 | res.status(500).json({ message: "Profile Updation Failed" }); 115 | } 116 | }; 117 | -------------------------------------------------------------------------------- /backend/middleware.js: -------------------------------------------------------------------------------- 1 | // Import necessary modules and packages 2 | const Token = require('./models/tokenModel'); // Import the Token model for working with tokens 3 | const jwt = require("jsonwebtoken"); // Import the JSON Web Token (JWT) package 4 | const catchAsync = require("./utils/catchAsync"); // Import the catchAsync utility function 5 | // Function to generate a JWT token for a user and set it in a cookie 6 | const generatejwt = async (id) => { 7 | // Extract user information like _id and email 8 | 9 | // Create a JWT token with user data and a secret key, set to expire in 2 hours 10 | const token = jwt.sign( 11 | { id }, 12 | process.env.TOKEN_KEY, // Use the TOKEN_KEY from environment variables as the secret key 13 | { 14 | expiresIn: "2h", // Token expires in 2 hours 15 | } 16 | ); 17 | 18 | // Set the token in a cookie named "token" 19 | 20 | // Create a new Token record in the database to keep track of tokens 21 | const tokenRecord = new Token({ token }); 22 | 23 | // Save the token record to the database 24 | await tokenRecord.save(); 25 | 26 | return token 27 | 28 | } 29 | 30 | // Function to clear the "token" cookie on the client side 31 | const clearTokenCookie = (res) => { 32 | res.clearCookie("token"); 33 | }; 34 | 35 | // Function to log out the user by clearing the token on both client and server sides 36 | const logout = async (req, res) => { 37 | try { 38 | const token = req.cookies.token; 39 | 40 | if (!token) { 41 | return res.status(200).json({ message: "Logged out successfully" }); 42 | } 43 | 44 | // Delete the token from the database (if it exists) 45 | await Token.findOneAndDelete({ token }); 46 | 47 | // Clear the token cookie on the client side 48 | clearTokenCookie(res); 49 | 50 | res.status(200).json({ message: "Logged out successfully" }); 51 | } catch (error) { 52 | console.error("Logout error:", error); 53 | res.status(500).json({ message: "Internal server error" }); 54 | } 55 | }; 56 | 57 | const verifyToken = catchAsync(async (req, res, next) => { 58 | const bearer = req.cookies.token; 59 | 60 | if (!bearer) return res.status(401).json({ message: "You are not Authenticated" }); 61 | 62 | try { 63 | const authData = jwt.verify(bearer, process.env.TOKEN_KEY); 64 | 65 | return res.status(200).json({ 66 | status: "success", 67 | data: { authData }, 68 | }); 69 | 70 | } catch (error) { 71 | return res.status(401).json({ 72 | status: "failure", 73 | data: { error }, 74 | }); 75 | } 76 | }); 77 | 78 | // Middleware function to verify the JWT token from a cookie and authenticate the user 79 | const verifyjwt = async (req, res, next) => { 80 | try { 81 | // Extract the JWT token from the "token" cookie 82 | const token = req.cookies.token; 83 | 84 | // If no token is found, return an Unauthorized response (HTTP 401) 85 | if (!token) { 86 | return res.status(401).json({ message: "Unauthorized" }); 87 | } 88 | 89 | // Verify the token using the secret key (TOKEN_KEY) from environment variables 90 | const decoded = jwt.verify(token, process.env.TOKEN_KEY); 91 | 92 | // Check if the token exists in the database (previously generated by generatejwt) 93 | const tokenRecord = await Token.findOne({ token }); 94 | 95 | // If the token does not exist in the database, return Unauthorized 96 | if (!tokenRecord) { 97 | return res.status(401).json({ message: "You are not Loggedin please Login" }); 98 | } 99 | 100 | // Attach the decoded user data to the request object for further use 101 | req.user = decoded; 102 | 103 | // Continue to the next middleware or route 104 | next(); 105 | } catch { 106 | return res.status(401).json({ message: "Unauthorized" }); 107 | } 108 | }; 109 | 110 | // Export the generatejwt and verifyjwt functions for use in other parts of the application 111 | module.exports = { generatejwt, verifyjwt, logout, verifyToken }; 112 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Music-Streaming-App 2 | 3 | ## Backend for Music Streaming App 4 | 5 | Welcome to the backend section for Music Streaming App! This repository contains the server-side code responsible for handling user authentication, managing playlists, and serving music-related data. This README will guide you through setting up the backend code and provide an overview of its structure and functionality. 6 | 7 | ### Table of Contents 8 | - [Prerequisites](#prerequisites) 9 | - [Getting Started](#getting-started) 10 | - [Folder Structure](#folder-structure) 11 | - [Environment Variables](#environment-variables) 12 | - [Middleware](#middleware) 13 | - [Routes](#routes) 14 | - [Controllers](#controllers) 15 | - [Models](#models) 16 | - [Database](#database) 17 | - [Authentication](#authentication) 18 | - [Endpoints](#endpoints) 19 | - [Contributing](#contributing) 20 | - [License](#license) 21 | 22 | ### Prerequisites 23 | Before setting up the backend, make sure you have the following software installed on your system: 24 | - Node.js (v14.x or higher) 25 | - MongoDB (v4.x or higher) 26 | - Git (optional, but recommended for version control) 27 | 28 | ### Getting Started 29 | Follow these steps to get the backend up and running on your local machine: 30 | 31 | 1. Clone the repository: 32 | ```bash 33 | git clone https://github.com/your-username/Music-Streaming-App.git 34 | ``` 35 | 36 | 2. Navigate to the project directory: 37 | ```bash 38 | cd Music-Streaming-App 39 | ``` 40 | 41 | 3. Install dependencies: 42 | ```bash 43 | npm install 44 | ``` 45 | 46 | 4. Create a `.env` file in the root directory and set the necessary environment variables (see [Environment Variables](#environment-variables)). 47 | 48 | 5. Start the server: 49 | ```bash 50 | npm start 51 | ``` 52 | 53 | 6. Your backend server should now be running on `http://localhost:5000`. 54 | 55 | ### Folder Structure 56 | The backend code is organized into the following main directories: 57 | 58 | - **middleware**: Contains custom middleware functions used for authentication and request handling. 59 | - **models**: Defines the data models (e.g., User, Song, Playlist) using Mongoose. 60 | - **routes**: Contains route definitions for different API endpoints. 61 | - **controllers**: Implements the logic for handling HTTP requests. 62 | - **config**: Houses configuration files, if any. 63 | - **uploads**: Stores uploaded files (e.g., song audio files). 64 | - **tests**: Contains test files and configurations (if testing is implemented). 65 | 66 | ### Environment Variables 67 | To run the backend code successfully, you need to set the following environment variables in your `.env` file: 68 | 69 | - `MONGODB`: MongoDB connection URI. 70 | - `TOKEN_KEY`: Secret key for JWT (JSON Web Token) generation. 71 | - (Additional variables based on your specific configuration, if any.) 72 | 73 | Here's an example `.env` file: 74 | 75 | ```env 76 | MONGODB=mongodb://localhost:27017/your-music-app 77 | TOKEN_KEY=your-secret-key 78 | ``` 79 | 80 | ### Middleware 81 | The `middleware` directory contains functions responsible for various tasks, such as user authentication (`verifyjwt`), handling cookies, and error handling. Middleware functions are used in the route definitions to add specific functionality to the routes. 82 | 83 | ### Routes 84 | Routes define the API endpoints and their associated middleware and controller functions. Each route file is responsible for a specific set of related endpoints (e.g., user routes, song routes, playlist routes). 85 | 86 | ### Controllers 87 | Controllers implement the logic for handling HTTP requests. They interact with the data models and services to perform actions such as user registration, song upload, playlist management, and more. 88 | 89 | ### Models 90 | The `models` directory defines the data schemas using Mongoose. Each model (e.g., User, Song, Playlist) corresponds to a collection in the MongoDB database and provides methods for interacting with that data. 91 | 92 | ### Database 93 | The backend uses MongoDB as its database. Make sure you have MongoDB installed and running. You can configure the database connection URI in the `.env` file. 94 | 95 | ### Authentication 96 | The backend uses JWT (JSON Web Tokens) for user authentication. When a user registers or logs in, a token is generated and stored in a cookie for subsequent authentication on protected routes. 97 | 98 | ### Endpoints 99 | The backend provides various endpoints for user registration, authentication, song management, playlist management, and more. Each endpoint is documented in the route files, and you can test them using tools like Postman or by integrating them with your frontend. 100 | 101 | ### Contributing 102 | Feel free to contribute to the development of the backend by submitting issues, feature requests, or pull requests. Follow the standard Git branching workflow and maintain a clean and well-documented codebase. 103 | 104 | ### License 105 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 106 | 107 | Thank you for using this backend code for my Music App! If you have any questions or need further assistance, please don't hesitate to reach out to the maintainers of this repository. 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 |

Groovewave

6 |

Ride the GrooveWave: Your Sound, Your Vibe, Your Way!

7 | 8 | 9 |

10 | 11 | contributors 12 | 13 | 14 | last update 15 | 16 | 17 | forks 18 | 19 | 20 | stars 21 | 22 | 23 | open issues 24 | 25 | 26 | license 27 | 28 |

29 | 30 | 31 |

32 | View Demo 33 | · 34 | Documentation 35 | · 36 | Report Bug 37 | · 38 | Request Feature 39 |

40 |
41 |
42 | 43 | 44 | ## Table of Contents 45 | - [Groovewave Music Streaming App](#groovewave-music-streaming-app) 46 | - [Tech Stack](#tech-stack) 47 | - [Getting Started](#getting-started) 48 | - [Installation](#installation) 49 | - [Backend Setup](#backend-setup) 50 | - [Frontend Setup](#frontend-setup) 51 | - [Deployment](#deployment) 52 | - [Hacktoberfest](#hacktoberfest) 53 | - [Contributing](#contributing) 54 | - [Contributors](#contributors) 55 | - [License](#license) 56 | 57 | 58 | # Groovewave Music Streaming App 59 | 60 | Our music streaming app redefines how you enjoy and discover music. 61 | 62 | ![image](https://github.com/ojasaklechayt/Music-Streaming-App/assets/90605717/23c2e493-6a1e-4fc7-8033-375b431e150b) 63 | 64 | 65 | ## Tech Stack 66 | - Front-End: 67 | * ![Next.js](https://img.shields.io/badge/nextjs-black?style=for-the-badge&logo=next.js&logoColor=white) 68 | * ![Tailwind CSS](https://img.shields.io/badge/tailwindcss-%2338B2AC?style=for-the-badge&logo=tailwind-css&logoColor=white) 69 | - Linting and Code Quality: 70 | * ![ESLint](https://img.shields.io/badge/eslint-3A33D1?style=for-the-badge&logo=eslint&logoColor=white) 71 | - Backend: 72 | * ![Express.js](https://img.shields.io/badge/expressjs-%23404d59?style=for-the-badge&logo=express&logoColor=%2361DAFB) 73 | * ![MongoDB](https://img.shields.io/badge/mongodb-%2347A248?style=for-the-badge&logo=mongodb&logoColor=white) 74 | * ![Node.js](https://img.shields.io/badge/Node.js-43853D?style=for-the-badge&logo=node.js&logoColor=white) 75 | 76 | ## Getting Started 77 | 78 | ### Installation 79 | 1. Fork the repository 80 | 81 | 2. Clone your forked copy of the project 82 | ``` 83 | git clone https://github.com//Music-Streaming-App.git 84 | ``` 85 | 3. Navigate to the project directory 86 | ``` 87 | cd Music-Streaming-App 88 | ``` 89 | 90 | ### Backend Setup 91 | For the backend, navigate to the "backend" directory: 92 | 93 | ```bash 94 | cd backend 95 | npm install 96 | npm run server 97 | ``` 98 | 99 | ### Frontend Setup 100 | To set up the frontend, navigate to the "frontend" directory: 101 | 102 | ```bash 103 | cd frontend 104 | npm install 105 | npm run dev 106 | ``` 107 | ### Deployment 108 | 109 | You can deploy this project to any static site hosting service. 110 | 111 | Some of the popular ones are: 112 | 113 | - [Vercel](https://vercel.com/) (Recommended for static sites) 114 | - [Netlify](https://www.netlify.com/) 115 | - [Railway](https://railway.app/) (Ideal for full-stack MERN applications) 116 | 117 | ## Hacktoberfest 118 | 119 | 1. Star the repo if you like it. ⭐ 120 | 2. Please read the [rules](https://hacktoberfest.com/participation/) before opening a pull request. 121 | 3. The maintainer(s) will add the `hacktoberfest-accepted` label after reviewing and accepting your pull request. 122 | 4. You can also create your own issue if you have something in mind. 123 | 5. Low quality PRs will not be merged 124 | 125 | ## Contributing 126 | 127 | [Contributing Guide](https://github.com/ojasaklechayt/Music-Streaming-App/blob/main/CONTRIBUTING.md) 128 | 129 | Feel free to open an issue if you find a bug or want to suggest a feature. 130 | 131 | 132 | ## Contributors 133 | 134 | 135 | 136 | 137 | 138 | Made with [contrib.rocks](https://contrib.rocks). 139 | 140 | ## License 141 | 142 | [CCO License](LICENSE) 143 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. -------------------------------------------------------------------------------- /frontend/app/login/page.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState } from "react"; 3 | import { ToastContainer, toast } from "react-toastify"; 4 | import "react-toastify/dist/ReactToastify.css"; 5 | import Navbar from "@/components/navbar"; 6 | import Abstract from "../../public/abstract_figures.png"; 7 | import Link from "next/link"; // Make sure to import Link 8 | import Image from "next/image"; 9 | import axios from "axios"; 10 | import { useRouter } from "next/navigation"; 11 | import { Cookie } from "next/font/google"; 12 | import Cookies from "js-cookie"; 13 | export default function Login() { 14 | const router = useRouter(); 15 | const [formData, setFormData] = useState({ 16 | email: "", 17 | password: "", 18 | }); 19 | 20 | const handleInputChange = (e) => { 21 | const { name, value } = e.target; 22 | setFormData({ 23 | ...formData, 24 | [name]: value, 25 | }); 26 | }; 27 | 28 | const handleLogin = async (e) => { 29 | e.preventDefault(); 30 | 31 | try { 32 | const response = await axios.post( 33 | "https://music-streaming-app.onrender.com/users/login", 34 | formData, 35 | { 36 | withCredentials: true, 37 | headers: { 38 | "Access-Control-Allow-Origin": "*", 39 | "Content-Type": "application/json", 40 | }, 41 | credentials: "include", 42 | } 43 | ); 44 | console.log("Login successful:", response); 45 | 46 | const { token } = response.data; 47 | if (token) { 48 | toast.success("User Login successfully"); 49 | 50 | // Set the token in a cookie 51 | Cookies.set("token", token, { expires: 2 / 24 }); 52 | 53 | // Redirect to the home page 54 | router.push("/home"); 55 | } 56 | } catch (error) { 57 | if (error.response) { 58 | if (error.response.status === 401) { 59 | toast.error(error.response.data.message); 60 | } else { 61 | toast.error("User Login failed"); 62 | console.error("Login failed:", error); 63 | } 64 | } else { 65 | toast.error("An error occurred while logging in."); 66 | console.error("Login failed:", error); 67 | } 68 | } 69 | }; 70 | 71 | return ( 72 |
73 | 74 |
75 |
76 |
77 | 78 |
79 | 80 | {/* Login form */} 81 | 82 |
83 |
84 |
85 |
86 |

87 | LOGIN 88 |

89 |
93 |
94 | 100 | 110 |
111 |
112 | 118 | 128 |
129 | 130 | 136 |
137 |

138 | Don't have an account?{" "} 139 |

140 | 141 |

142 | 146 | Register Now! 147 | 148 |

149 | 150 |
151 |
152 |
153 |
154 |
155 |
156 | 167 |
168 | ); 169 | } 170 | -------------------------------------------------------------------------------- /frontend/app/register/page.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useState } from "react"; 3 | import { ToastContainer, toast } from "react-toastify"; 4 | import "react-toastify/dist/ReactToastify.css"; 5 | import Navbar from "@/components/navbar"; 6 | import Abstract from "../../public/abstract_figures.png"; 7 | import Link from "next/link"; // Make sure to import Link 8 | import Image from "next/image"; 9 | import axios from "axios"; 10 | 11 | import { useRouter } from "next/navigation"; 12 | 13 | export default function Register() { 14 | const router = useRouter(); 15 | 16 | const handleRegister = async (e) => { 17 | e.preventDefault(); 18 | 19 | try { 20 | const response = await axios.post( 21 | "https://music-streaming-app.onrender.com/users/register", 22 | formData 23 | ); 24 | console.log("Registration successful:", response); 25 | if (response.status === 201) { 26 | toast.success("User Registered Successfully"); 27 | router.push("/login"); 28 | } 29 | } catch (error) { 30 | if (error.response) { 31 | if ( 32 | error.response.status === 400 && 33 | error.response.data.message === "User already exists" 34 | ) { 35 | toast.error("User already exists"); 36 | } else { 37 | console.log(error); 38 | toast.error("User Registration failed"); 39 | } 40 | } else { 41 | console.log(error); 42 | toast.error("An error occurred while registering."); 43 | } 44 | } 45 | }; 46 | 47 | const handleInputChange = (e) => { 48 | const { name, value } = e.target; 49 | setFormData({ 50 | ...formData, 51 | [name]: value, 52 | }); 53 | }; 54 | 55 | const [formData, setFormData] = useState({ 56 | username: "", 57 | email: "", 58 | password: "", 59 | }); 60 | 61 | return ( 62 |
63 | 64 |
65 |
66 |
67 | 68 |
69 | 70 | {/* Register form */} 71 | 72 |
73 |
74 |
75 |
76 |

77 | REGISTER 78 |

79 |
80 |
81 | 87 | 96 |
97 |
98 | 104 | 113 |
114 |
115 | 121 | 130 |
131 | 132 | 139 |
140 |

141 | Already have an account?{" "} 142 |

143 | 144 |

145 | 149 | Login Now! 150 | 151 |

152 | 153 |
154 |
155 |
156 |
157 |
158 |
159 | 170 |
171 | ); 172 | } 173 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /backend/controllers/songController.js: -------------------------------------------------------------------------------- 1 | // Import necessary models 2 | const Song = require('../models/songModel'); 3 | const User = require('../models/userModel'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | // Controller functions for song-related actions 7 | 8 | // Get all songs 9 | exports.getAllSongs = async (res) => { 10 | try { 11 | // Find all songs in the database 12 | const songs = await Song.find(); 13 | res.status(200).json({ songs }); 14 | } catch (error) { 15 | console.error('Error Fetching All The Songs: ', error); 16 | res.status(500).json({ message: "Error Fetching All The Songs" }) 17 | } 18 | } 19 | 20 | // Get a song by name 21 | exports.getSongByName = async (req, res) => { 22 | try { 23 | const { name } = req.body; 24 | 25 | // Find a song with the provided name 26 | const song = await Song.findOne({ name }); 27 | 28 | if (!song) { 29 | return res.status(404).json({ song: [] }); 30 | } 31 | res.status(200).json({ song }); 32 | } catch (error) { 33 | console.error("Error Fetching Specific Songs by Name: ", error); 34 | res.status(500).json({ message: "Error Fetching Specific Songs by Name" }); 35 | } 36 | } 37 | 38 | // Upload a new song in form-data format 39 | exports.uploadSong = async (req, res) => { 40 | try { 41 | const { title, artist, genre, owner } = req.body; 42 | 43 | // Find the user who is the owner of the song 44 | const user = await User.findById(owner); 45 | 46 | if (!user) { 47 | return res.status(404).json({ message: "User Not Found" }); 48 | } 49 | 50 | const audioData = req.files.audio.data; 51 | const audioContentType = req.files.audio.mimetype; 52 | 53 | // Create a new song document with the provided data and audio 54 | const newSong = new Song({ 55 | title, 56 | artist, 57 | genre, 58 | owner, 59 | audio: { 60 | data: audioData, 61 | contentType: audioContentType, 62 | }, 63 | }); 64 | 65 | // Save the new song to the database 66 | await newSong.save(); 67 | 68 | // Update the user's uploaded songs list 69 | user.uploadedSongs.push(newSong._id); 70 | await user.save(); 71 | 72 | res.status(201).json({ message: "Song Uploaded Successfully", song: newSong, user: user }); 73 | } catch (error) { 74 | console.error("Error Uploading Data: ", error); 75 | res.status(500).json({ message: "Error Uploading Data" }); 76 | } 77 | } 78 | 79 | // Get songs by artist 80 | exports.getSongByArtist = async (req, res) => { 81 | try { 82 | const { Artist } = req.query; 83 | 84 | // Find songs with the provided artist name 85 | const song = await Song.find({ Artist }); 86 | 87 | if (!song) { 88 | return res.status(404).json({ song: [] }); 89 | } 90 | res.status(200).json({ song }); 91 | } catch (error) { 92 | console.error("Error Fetching Specific Songs by Artist: ", error); 93 | res.status(500).json({ message: "Error Fetching Specific Songs by Artist" }); 94 | } 95 | } 96 | 97 | // Get songs by genre 98 | exports.getSongByGenre = async (req, res) => { 99 | try { 100 | const { Genre } = req.body; 101 | 102 | // Find songs with the provided genre 103 | const song = await Song.find({ Genre }); 104 | 105 | if (!song || song.length === 0) { 106 | return res.status(404).json({ song: [] }); 107 | } 108 | 109 | res.status(200).json({ song }); 110 | } catch (error) { 111 | console.error("Error Fetching Specific Songs by Genre: ", error); 112 | res.status(500).json({ message: "Error Fetching Specific Songs by Genre" }); 113 | } 114 | } 115 | 116 | // Delete a song 117 | exports.deleteSong = async (req, res) => { 118 | try { 119 | const userId = req.params.userId; 120 | const songID = req.params.songId; 121 | 122 | // Find the song by its ID 123 | const song = await Song.findById(songID); 124 | 125 | if (!song) { 126 | return res.status(404).json({ message: "Song Not Found" }); 127 | } 128 | 129 | // Find the user by their ID 130 | const user = await User.findById(userId); 131 | if (!user) { 132 | return res.status(404).json({ message: "User Not Found" }); 133 | } 134 | 135 | // Check if the user is the owner of the song 136 | if (song.owner.toString() !== userId.toString()) { 137 | return res.status(403).json("Unauthorized: You are not the owner of this song"); 138 | } 139 | 140 | // Remove the song from the user's uploaded songs list 141 | user.uploadedSongs.pull(songID); 142 | await user.save(); 143 | 144 | // Delete the song from the database 145 | await Song.deleteOne({ _id: songID }); 146 | 147 | res.status(204).end({ message: "Song Deleted Successfully!!" }); 148 | console.log("Song Deleted Successfully!!"); 149 | 150 | } catch (error) { 151 | console.error("Song Deletion Error: ", error); 152 | res.status(500).json({ message: "Song Deletion Error" }); 153 | } 154 | } 155 | 156 | exports.editSong = async (req, res) => { 157 | try { 158 | const songId = req.params.songId; 159 | const { title, artist, genre, SongPhoto } = req.body; 160 | const userId = req.body.userId; 161 | 162 | // Find the song by its ID 163 | const song = await Song.findById(songId); 164 | 165 | if (!song) { 166 | return res.status(404).json({ message: "Song Not Found" }); 167 | } 168 | 169 | // Check if the user is the owner of the song 170 | if (song.owner.toString() !== userId) { 171 | return res.status(403).json({ message: "Unauthorized: You are not the owner of this song" }); 172 | } 173 | 174 | // Convert the base64-encoded image data to a buffer 175 | const imageBuffer = Buffer.from(SongPhoto, 'base64'); 176 | 177 | // Generate a unique file name 178 | const uniqueFileName = `${songId}-${Date.now()}.jpg`; 179 | 180 | // Define the file path 181 | const filePath = path.join(__dirname, 'uploads', uniqueFileName); 182 | 183 | // Write the image data to the file 184 | fs.writeFileSync(filePath, imageBuffer); 185 | 186 | // Update the song data with the file path 187 | const updatedSong = await Song.findByIdAndUpdate(songId, { title, artist, genre, SongPhoto: filePath }, { new: true }); 188 | 189 | res.status(200).json(updatedSong); 190 | console.log("Song Edited Successfully!!"); 191 | } catch (error) { 192 | console.error("Error Editing Song: ", error); 193 | res.status(500).json({ message: "Song Editing Error" }); 194 | } 195 | } 196 | 197 | 198 | // Get songs by user 199 | exports.getSongByUser = async (req, res) => { 200 | try { 201 | const userId = req.params.userId; 202 | 203 | // Find songs owned by the user 204 | const song = await Song.find({ owner: userId }); 205 | 206 | if (!song) { 207 | return res.status(404).json({ message: "No Songs Found" }); 208 | } 209 | 210 | res.status(200).json(song); 211 | console.log("Songs Found"); 212 | } catch (error) { 213 | console.error("Error Editing Song: ", error); 214 | res.status(500).json({ message: "Song Editing Error" }); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /frontend/components/Player.js: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { useState, useEffect } from 'react'; 3 | import { FaPlay, FaPause, FaStepBackward, FaStepForward, FaVolumeUp, FaVolumeMute } from 'react-icons/fa'; 4 | 5 | const imagepath = "https://i1.sndcdn.com/artworks-ykhavMzzmobOTXYI-vm7RSg-t500x500.jpg"; 6 | 7 | const Player = () => { 8 | const [isPlaying, setIsPlaying] = useState(false); 9 | const [paused, setPaused] = useState(true); 10 | const [currentSongIndex, setCurrentSongIndex] = useState(0); 11 | const [changingSong, setChangingSong] = useState(false); 12 | const [volume, setVolume] = useState(50); // Initial volume (0-100) 13 | const [currentTime, setCurrentTime] = useState(0); 14 | const [songDuration, setSongDuration] = useState(30000); // Simulated song duration in milliseconds 15 | const [playbackPosition, setPlaybackPosition] = useState(0); 16 | 17 | // Dummy list of songs 18 | const songs = [ 19 | { 20 | title: 'Song 1', 21 | artist: 'Artist 1', 22 | url: '/song1.mp3', 23 | }, 24 | { 25 | title: 'Song 2', 26 | artist: 'Artist 2', 27 | url: '/song2.mp3', 28 | }, 29 | { 30 | title: 'Song 3', 31 | artist: 'Artist 3', 32 | url: '/song3.mp3', 33 | }, 34 | ]; 35 | 36 | const playPause = () => { 37 | if (paused) { 38 | // Start playing from the beginning 39 | setIsPlaying(true); 40 | setPaused(false); 41 | setCurrentTime(0); // Reset currentTime 42 | } else { 43 | // Pause the audio and store the current playback position 44 | setIsPlaying(false); 45 | setPaused(true); 46 | setPlaybackPosition(currentTime); 47 | } 48 | }; 49 | 50 | const previousSong = () => { 51 | if (currentSongIndex > 0) { 52 | setChangingSong(true); 53 | setTimeout(() => { 54 | setCurrentSongIndex(currentSongIndex - 1); 55 | setCurrentTime(0); // Reset currentTime 56 | setChangingSong(false); 57 | // Add logic to play the previous song here 58 | }, 500); // Simulated delay for the transition 59 | } 60 | }; 61 | 62 | const nextSong = () => { 63 | if (currentSongIndex < songs.length - 1) { 64 | setChangingSong(true); 65 | setTimeout(() => { 66 | setCurrentSongIndex(currentSongIndex + 1); 67 | setCurrentTime(0); // Reset currentTime 68 | setChangingSong(false); 69 | // Add logic to play the next song here 70 | }, 500); // Simulated delay for the transition 71 | } 72 | }; 73 | 74 | const handleVolumeChange = (e) => { 75 | setVolume(e.target.value); 76 | // Set the volume of the audio element here 77 | }; 78 | 79 | const isMuted = volume === 0; // Check if volume is muted 80 | 81 | useEffect(() => { 82 | if (isPlaying && !paused) { 83 | if (currentTime < songDuration) { 84 | const interval = setInterval(() => { 85 | if (isPlaying && !paused) { 86 | setCurrentTime((prevTime) => { 87 | if (prevTime + 100 <= songDuration) { 88 | return prevTime + 100; 89 | } else { 90 | setIsPlaying(false); 91 | setPaused(true); 92 | setCurrentTime(0); 93 | if (currentSongIndex < songs.length - 1) { 94 | nextSong(); 95 | } 96 | return prevTime; 97 | } 98 | }); 99 | } 100 | }, 100); 101 | 102 | return () => { 103 | clearInterval(interval); 104 | }; 105 | } 106 | } 107 | }, [currentSongIndex, isPlaying, paused, currentTime, nextSong, songDuration, songs]); 108 | 109 | useEffect(() => { 110 | if (isPlaying && !paused) { 111 | // When resuming, set the current time to the playback position 112 | setCurrentTime(playbackPosition); 113 | } 114 | }, [paused, playbackPosition, isPlaying]); 115 | 116 | return ( 117 |
118 |
119 |
120 |
121 |
122 | Music Icon 127 |
128 |

129 | {changingSong ? 'Changing Song...' : songs[currentSongIndex].title} 130 |

131 |

{changingSong ? '' : songs[currentSongIndex].artist}

132 |
133 |
134 |
135 | 141 | 147 | 153 |
154 | {isMuted ? ( 155 | setVolume(50)} // Toggle volume on mute icon click 158 | /> 159 | ) : ( 160 | setVolume(0)} // Toggle mute on volume icon click 163 | /> 164 | )} 165 | 173 |
174 |
175 |
176 |
177 |
178 |
182 |
183 |
184 |
185 |
186 | ); 187 | }; 188 | 189 | export default Player; 190 | -------------------------------------------------------------------------------- /backend/controllers/playlistController.js: -------------------------------------------------------------------------------- 1 | // Import necessary models 2 | const Playlist = require('../models/playlistModel'); 3 | const Song = require('../models/songModel'); 4 | const User = require('../models/userModel'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | 8 | // Controller functions for playlist-related actions 9 | 10 | // Create a new playlist 11 | exports.createPlaylist = async (req, res) => { 12 | try { 13 | const { name, id, songs, playlistPhoto } = req.body; 14 | 15 | // Find the user by their ID 16 | const user = await User.findById(id); 17 | 18 | if (!user) { 19 | return res.status(404).json({ message: "User Not Found" }); 20 | } 21 | 22 | // Create a new playlist with the provided name, creator ID, and songs 23 | const newPlaylist = new Playlist({ 24 | name, 25 | creator: id, 26 | songs: songs || [], 27 | playlistPhoto: '' // Initialize with an empty string for the file path 28 | }); 29 | 30 | if (playlistPhoto) { 31 | // Convert the base64-encoded image data to a buffer 32 | const imageBuffer = Buffer.from(playlistPhoto, 'base64'); 33 | 34 | // Generate a unique file name for the playlist's photo 35 | const uniqueFileName = `${newPlaylist._id}-${Date.now()}.jpg`; 36 | 37 | // Define the file path 38 | const filePath = path.join(__dirname, 'uploads', uniqueFileName); 39 | 40 | // Write the image data to the file 41 | fs.writeFileSync(filePath, imageBuffer); 42 | 43 | // Update the playlist's photo field with the file path 44 | newPlaylist.playlistPhoto = filePath; 45 | } 46 | 47 | // Save the new playlist 48 | await newPlaylist.save(); 49 | 50 | // Update the user's createdPlaylists with the new playlist's ID 51 | user.createdPlaylists.push(newPlaylist._id); 52 | await user.save(); 53 | 54 | res.status(201).json(newPlaylist); 55 | } catch (error) { 56 | console.error("Error Creating Playlist: ", error); 57 | res.status(500).json({ message: "Error Creating Playlist" }); 58 | } 59 | }; 60 | 61 | // Edit an existing playlist 62 | exports.editPlaylist = async (req, res) => { 63 | try { 64 | const { name, userId, playlistPhoto } = req.body; 65 | const playlistId = req.params.id; 66 | 67 | // Find the playlist by its ID 68 | const playlist = await Playlist.findById(playlistId); 69 | 70 | if (!playlist) { 71 | return res.status(404).json({ message: "Playlist Not Found" }); 72 | } 73 | 74 | if (!userId || typeof userId !== 'string') { 75 | return res.status(400).json({ message: "Invalid user ID" }); 76 | } 77 | 78 | // Check if the user is the owner of the playlist 79 | if (playlist.creator.toString() !== userId.toString()) { 80 | return res.status(403).json({ message: "Unauthorized: You are not the owner of this playlist" }); 81 | } 82 | 83 | // Update the playlist's name if provided 84 | if (name) { 85 | playlist.name = name; 86 | } 87 | 88 | if (playlistPhoto) { 89 | // Convert the base64-encoded image data to a buffer 90 | const imageBuffer = Buffer.from(playlistPhoto, 'base64'); 91 | 92 | // Generate a unique file name for the playlist's photo 93 | const uniqueFileName = `${playlist._id}-${Date.now()}.jpg`; 94 | 95 | // Define the file path 96 | const filePath = path.join(__dirname, 'uploads', uniqueFileName); 97 | 98 | // Write the image data to the file 99 | fs.writeFileSync(filePath, imageBuffer); 100 | 101 | // Update the playlist's photo field with the new file path 102 | playlist.playlistPhoto = filePath; 103 | } 104 | 105 | await playlist.save(); 106 | res.status(200).json({ playlist }); 107 | } catch (error) { 108 | console.error("Error Editing Playlist: ", error); 109 | res.status(500).json({ message: "Error Editing Playlist" }); 110 | } 111 | }; 112 | 113 | // Delete a playlist 114 | exports.deletePlaylist = async (req, res) => { 115 | try { 116 | const playlistId = req.params.id; 117 | 118 | // Find the playlist by its ID 119 | const playlist = await Playlist.findById(playlistId); 120 | 121 | if (!playlist) { 122 | return res.status(404).json({ message: "Playlist Not Found" }); 123 | } 124 | 125 | const userId = playlist.creator; 126 | 127 | // Find the user by their ID 128 | const user = await User.findById(userId); 129 | 130 | if (!user) { 131 | return res.status(404).json({ message: "User Not Found" }); 132 | } 133 | 134 | // Check if the user is the owner of the playlist 135 | if (playlist.creator.toString() !== userId.toString()) { 136 | return res.status(403).json({ message: "Unauthorized: You are not the owner of this playlist" }); 137 | } 138 | 139 | // Remove the playlist ID from the user's createdPlaylists 140 | user.createdPlaylists.pull(playlistId); 141 | await user.save(); 142 | 143 | // Delete the playlist 144 | await Playlist.deleteOne({ _id: playlistId }); 145 | 146 | res.status(204).end({ message: "Playlist Deleted Successfully!!" }); 147 | console.log("Playlist Deleted Successfully!!"); 148 | } catch (error) { 149 | console.error("Error Deleting Playlist: ", error); 150 | res.status(500).json({ message: "Error Deleting Playlist" }); 151 | } 152 | }; 153 | 154 | // Add a song to a playlist 155 | exports.addSongToPlaylist = async (req, res) => { 156 | try { 157 | const { songId } = req.body; 158 | const playlistId = req.params.id; 159 | 160 | // Find the playlist by its ID 161 | const playlist = await Playlist.findById(playlistId); 162 | 163 | if (!playlist) { 164 | return res.status(404).json({ message: "Playlist Not Found" }); 165 | } 166 | 167 | // Find the song by its ID 168 | const song = await Song.findById(songId); 169 | 170 | if (!song) { 171 | return res.status(404).json({ message: "Song Not Found" }); 172 | } 173 | 174 | // Check if the song already exists in the playlist 175 | const songExistsInPlaylist = playlist.songs.some((playlistSong) => 176 | playlistSong.equals(song._id) 177 | ); 178 | 179 | if (songExistsInPlaylist) { 180 | return res.status(400).json({ message: "Song already exists in the playlist" }); 181 | } 182 | 183 | // Add the song to the playlist's songs array 184 | playlist.songs.push(song); 185 | await playlist.save(); 186 | res.status(200).json(playlist); 187 | } catch (error) { 188 | console.error("Error Adding Songs To The Playlist: ", error); 189 | res.status(500).json({ message: "Error Adding Songs To The Playlist" }); 190 | } 191 | }; 192 | 193 | // Remove a song from a playlist 194 | exports.removeSongFromPlaylist = async (req, res) => { 195 | try { 196 | const playlistId = req.params.id; 197 | const songId = req.params.songId; 198 | 199 | // Find the playlist by its ID 200 | const playlist = await Playlist.findById(playlistId); 201 | 202 | if (!playlist) { 203 | return res.status(404).json({ message: "Playlist Not Found " }); 204 | } 205 | 206 | // Remove the song ID from the playlist's songs array 207 | playlist.songs.pull(songId); 208 | await playlist.save(); 209 | 210 | res.status(200).json(playlist); 211 | } catch (error) { 212 | console.error("Error Deleting Songs From The Playlist: ", error); 213 | res.status(500).json({ message: "Error Deleting Songs From The Playlist" }); 214 | } 215 | }; 216 | 217 | // Find playlists created by a specific user 218 | exports.findPlaylistsByUser = async (req, res) => { 219 | try { 220 | const userId = req.params.userId; 221 | 222 | // Find playlists with the specified creator (user ID) 223 | const playlists = await Playlist.find({ creator: userId }); 224 | 225 | res.status(200).json(playlists); 226 | console.log("Here're The Playlists"); 227 | } catch (error) { 228 | console.error('Error Fetching Playlists For User: ', error); 229 | res.status(500).json({ message: "Error Fetching Playlists For User" }); 230 | } 231 | }; 232 | 233 | // Get all playlists 234 | exports.getAllPlaylists = async (res) => { 235 | try { 236 | // Find and retrieve all playlists 237 | const playlists = await Playlist.find(); 238 | 239 | if (!playlists || playlists.length === 0) { 240 | return res.status(404).json({ message: "No Playlist Found" }); 241 | } 242 | 243 | res.status(200).json(playlists); 244 | } catch (error) { 245 | console.error('Error Getting All The Playlist: ', error); 246 | res.status(500).json({ message: "Error Getting All The Playlists" }); 247 | } 248 | }; 249 | 250 | // Get a specific playlist by ID 251 | exports.getPlaylistbyId = async (req, res) => { 252 | try { 253 | const playlistId = req.params.id; 254 | 255 | // Find the playlist by its ID 256 | const playlist = await Playlist.find({ playlistId }); 257 | 258 | if (!playlist || playlist.length === 0) { 259 | return res.status(404).json({ message: "No Playlist Found by ID" }); 260 | } 261 | 262 | res.status(200).json(playlist); 263 | } catch (error) { 264 | console.error('Error Fetching Specific Playlist: ', error); 265 | res.status(500).json({ message: "Error Fetching Specific Playlist" }); 266 | } 267 | }; 268 | 269 | // Get all songs from a specific playlist 270 | exports.getAllSongsFromPlaylist = async (req, res) => { 271 | try { 272 | const playlistId = req.params.id; 273 | 274 | // Find the playlist by its ID 275 | const playlist = await Playlist.findById(playlistId); 276 | 277 | if (!playlist) { 278 | return res.status(404).json({ message: "Playlist Not Found" }); 279 | } 280 | 281 | // Find all songs in the playlist's songs array 282 | const songs = await Song.find({ _id: { $in: playlist.songs } }); 283 | 284 | res.status(200).json(songs); 285 | } catch (error) { 286 | console.error('Error Fetching Songs From Playlist: ', error); 287 | res.status(500).json({ message: "Error Fetching Songs From Playlist" }); 288 | } 289 | }; 290 | -------------------------------------------------------------------------------- /frontend/public/groovewave.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | --------------------------------------------------------------------------------