├── .gitattributes ├── .gitignore ├── README.md ├── client ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src │ ├── App.css │ ├── App.js │ ├── components │ ├── Admin │ │ ├── Admin.js │ │ └── MyModals │ │ │ ├── AddAlbum.js │ │ │ ├── AddArtist.js │ │ │ ├── AddPlayList.js │ │ │ ├── AddSong.js │ │ │ ├── AddSongToPlayList.js │ │ │ ├── AddUser.js │ │ │ └── MyModal.css │ ├── Albums │ │ ├── Album.css │ │ ├── Album.js │ │ ├── ListOfAlbums.css │ │ ├── ListOfAlbums.js │ │ ├── OneAlbum.css │ │ └── OneAlbum.js │ ├── Artists │ │ ├── Artist.css │ │ ├── Artist.js │ │ ├── ListOfArtists.js │ │ ├── OneArtist.css │ │ └── OneArtist.js │ ├── Home │ │ ├── ElementToCarusel.js │ │ ├── Home.css │ │ └── Home.js │ ├── LogIn │ │ ├── Login.js │ │ ├── forms.css │ │ └── registaer.js │ ├── MyLibrary │ │ ├── MyLibrary.css │ │ └── MyLibrary.js │ ├── NavBar │ │ ├── NavBar.css │ │ └── NavBar.js │ ├── Network │ │ ├── Ajax.js │ │ └── Network.js │ ├── Playlists │ │ ├── ListOfPlaylists.css │ │ ├── ListOfPlaylists.js │ │ ├── OnePlaylist.js │ │ ├── PlayList.css │ │ └── PlayList.js │ ├── ReadMore │ │ └── ReadMore.js │ ├── Services │ │ ├── Loading.js │ │ ├── NotFound.js │ │ ├── globalVariables.js │ │ ├── share.js │ │ └── useContextComp.js │ ├── SideBar │ │ ├── SideBar.css │ │ └── SideBar.js │ └── Songs │ │ ├── ListOfSongs.css │ │ ├── ListOfSongs.js │ │ ├── OneSong.css │ │ ├── OneSong.js │ │ ├── Song.js │ │ └── SongsListForOneSong.js │ ├── images │ ├── Hilarious.gif │ ├── SongIcon.png │ ├── addToPlayList.png │ ├── admin.png │ ├── album.png │ ├── artist.png │ ├── disLike.png │ ├── dislikeActive.png │ ├── home.png │ ├── like.png │ ├── likeActive.png │ ├── myLibrary.png │ ├── pageNotFound.png │ ├── playbutton.png │ ├── playlist.png │ ├── shareButton.png │ ├── shareButton1.png │ └── userName.png │ ├── index.js │ ├── serviceWorker.js │ └── setupTests.js ├── readme-files ├── MySpotifyDB.txt ├── logo-main.png ├── my-ERD.png └── my-app.gif └── server ├── Routes └── users.js ├── api ├── index.js └── v1 │ ├── albums.js │ ├── artists.js │ ├── index.js │ ├── interactionRoues │ ├── albumsInteractions.js │ ├── artistsInteractions.js │ ├── playlistsInteractions.js │ └── songsInteractions.js │ ├── interactions.js │ ├── playlists.js │ ├── songs.js │ ├── songsInPlaylists.js │ └── users.js ├── config └── config.js ├── connection.js ├── helpers ├── checkAdmin.js ├── errorHandler.js ├── morgan.js ├── tokenCheck.js └── unknownEndpoint.js ├── migrations ├── 20200923121830-create-song.js ├── 20200923122007-create-album.js ├── 20200923122208-create-artist.js ├── 20200923122510-create-interaction.js ├── 20200923122629-create-playlists-song.js ├── 20200923122713-create-playlist.js ├── 20200923123105-create-user.js ├── 20200923123922-create-user-album.js ├── 20200923124017-create-user-artist.js ├── 20200923124102-create-user-playlist.js ├── 20200923124239-create-user-song.js ├── 20200923133639-types-songs.js ├── 20200923140433-types-iteractionss.js ├── 20200923140707-types-users.js ├── 20200923140853-types-albums.js ├── 20200923140957-types-artists.js ├── 20200923141030-types-playlists.js ├── 20200923141057-types-playlists_songs.js ├── 20200923141201-types-user_albums.js ├── 20200923141421-types-user_artists.js ├── 20200923141527-types-user_playlists.js ├── 20200923141632-types-user_songs.js ├── 20200923163228-songs_type.js ├── 20200924211319-paranoid.js ├── 20200924211637-underscore.js └── 20200927040244-add-isLiken-columns.js ├── models ├── album.js ├── artist.js ├── index.js ├── interaction.js ├── playlist.js ├── playlists_song.js ├── song.js ├── user.js ├── user_album.js ├── user_artist.js ├── user_playlist.js └── user_song.js ├── package-lock.json ├── package.json ├── server.js ├── start.js └── tests ├── albums.test.js ├── artists.test.js ├── playlists.test.js ├── songs.test.js ├── songsInPlaylists.test.js └── users.test.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Scale-Up Velocity](./readme-files/logo-main.png) SQL Music Streaming service 2 | 3 | This is my spotify app! 4 | The app serves a song interface try to similar spotify. 5 | The app include Authentication+token session, node+express, mysql DB, sequelize ORM, and tests. 6 | 7 | my gif: 8 | # ![gif](./readme-files/my-app.gif) 9 | 10 | my ERD: 11 | # ![pictue](./readme-files/my-ERD.png) 12 | 13 | my DataBase: 14 | https://github.com/david35008/my-spotify/blob/for-submit/readme-files/MySpotifyDB.txt 15 | -------------------------------------------------------------------------------- /client/.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 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `npm run build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "proxy": "http://localhost:8080/", 6 | "dependencies": { 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.5.0", 9 | "@testing-library/user-event": "^7.2.1", 10 | "bootstrap": "^4.5.2", 11 | "js-cookie": "^2.2.1", 12 | "react": "^16.13.1", 13 | "react-bootstrap": "^1.3.0", 14 | "react-dom": "^16.13.1", 15 | "react-elastic-carousel": "^0.7.5", 16 | "react-hook-form": "^6.8.3", 17 | "react-player": "^2.6.2", 18 | "react-router-dom": "^5.2.0", 19 | "react-scripts": "3.4.3", 20 | "react-share": "^4.3.0", 21 | "react-youtube": "^7.12.0", 22 | "styled-components": "^5.2.0" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "react-scripts test", 28 | "eject": "react-scripts eject" 29 | }, 30 | "eslintConfig": { 31 | "extends": "react-app" 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | My Spotify 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/public/logo512.png -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | body { 6 | margin-top: 55px; 7 | color: white; 8 | background-color: #0D0F10; 9 | } 10 | 11 | ::-webkit-scrollbar { 12 | width: 10px; 13 | } 14 | 15 | ::-webkit-scrollbar-track { 16 | box-shadow: inset 0 0 5px grey; 17 | border-radius: 10px; 18 | } 19 | 20 | ::-webkit-scrollbar-thumb { 21 | /* background: red; */ 22 | border-radius: 10px; 23 | height: 2px; 24 | } 25 | 26 | ::-webkit-scrollbar-thumb:hover { 27 | background: #b30000; 28 | } 29 | 30 | 31 | .goBack { 32 | border: none; 33 | background-color: transparent; 34 | color: white; 35 | font-size: 50px; 36 | } 37 | 38 | .goBack:hover { 39 | text-decoration: underline; 40 | } 41 | 42 | .goHome { 43 | font-size: 100px; 44 | } 45 | 46 | .brcpxa { 47 | color: transparent !important; 48 | background-color: transparent !important; 49 | box-shadow: none !important; 50 | } 51 | 52 | .ikyGAt:hover { 53 | box-shadow: none !important; 54 | } 55 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; 3 | import Home from './components/Home/Home'; 4 | import ListOfAlbums from './components/Albums/ListOfAlbums'; 5 | import OneAlbum from './components/Albums/OneAlbum'; 6 | import ListOfArtists from './components/Artists/ListOfArtists'; 7 | import OneArtist from './components/Artists/OneArtist'; 8 | import ListOfPlaylists from './components/Playlists/ListOfPlaylists'; 9 | import OnePlaylist from './components/Playlists/OnePlaylist'; 10 | import NavBar from './components/NavBar/NavBar'; 11 | import NotFound from './components/Services/NotFound' 12 | import OneSong from './components/Songs/OneSong'; 13 | import Admin from './components/Admin/Admin'; 14 | import LogIn from './components/LogIn/Login'; 15 | import Registaer from './components/LogIn/registaer'; 16 | import MyLibrary from './components/MyLibrary/MyLibrary'; 17 | import { Logged } from './components/Services/useContextComp'; 18 | import Cookies from 'js-cookie'; 19 | import { create } from './components/Network/Ajax'; 20 | 21 | function App() { 22 | const [isLogged, setIsLogged] = useState(false) 23 | const [loading, setLoading] = useState(true) 24 | const [isAdmin, setIsAdmin] = useState(false) 25 | 26 | useEffect(() => { 27 | if (Cookies.get('token')) { 28 | create('/users/valid', Cookies.get()) 29 | .then(res => { 30 | setIsAdmin(res.isAdmin); 31 | setIsLogged(res.valid); 32 | setLoading(false); 33 | }) 34 | .catch(err => { setLoading(false); setIsLogged(false); console.error(err); }) 35 | } else { 36 | setLoading(false) 37 | setIsLogged(false) 38 | } 39 | }, []) 40 | 41 | return ( 42 |
43 | 44 | {!loading ? 45 | !isLogged ? 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | : 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | {isAdmin && ( 88 | 89 | 90 | )} 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | : 100 |
101 | } 102 | 103 |
104 |
105 | ); 106 | } 107 | 108 | export default App; 109 | -------------------------------------------------------------------------------- /client/src/components/Admin/Admin.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { create } from '../Network/Ajax'; 3 | import { useHistory } from 'react-router-dom'; 4 | import { removeTokents } from '../Services/globalVariables'; 5 | import Cookies from 'js-cookie'; 6 | import NavBar from '../NavBar/NavBar'; 7 | import AddSong from './MyModals/AddSong'; 8 | import AddAlbum from './MyModals/AddAlbum'; 9 | import AddArtist from './MyModals/AddArtist'; 10 | import AddPlayList from './MyModals/AddPlayList'; 11 | import AddUser from './MyModals/AddUser'; 12 | import AddSongToPlayList from './MyModals/AddSongToPlayList'; 13 | 14 | function Admin() { 15 | 16 | const [openSongModal, setOpenSongModal] = useState(false); 17 | const [openAlbumModal, setOpenAlbumModal] = useState(false); 18 | const [openArtistModal, setOpenArtistModal] = useState(false); 19 | const [openPlayListModal, setOpenPlayListModal] = useState(false); 20 | const [openUserModal, setOpenUserModal] = useState(false); 21 | const [OpenSongToPlayListModal, setOpenSongToPlayListModal] = useState(false); 22 | const history = useHistory() 23 | 24 | function pad(num) { return ('00' + num).slice(-2) }; 25 | 26 | // Change the date to SQL date format 27 | function formatDate(date) { 28 | let dateStr = date.getUTCFullYear() + '-' + 29 | pad(date.getUTCMonth() + 1) + '-' + 30 | pad(date.getUTCDate() + 1) 31 | return dateStr; 32 | }; 33 | 34 | useEffect(() => { 35 | create('/users/valid', Cookies.get()) 36 | .then(res => { 37 | if(res.isAdmin === false) { 38 | removeTokents(); 39 | history.push('/'); 40 | } 41 | }) 42 | .catch(console.error) 43 | }, [history]) 44 | 45 | return ( 46 | <> 47 | 48 |

This is an admin page

49 | 50 | {openSongModal && } 51 | 52 | {openArtistModal && } 53 | 54 | {openAlbumModal && } 55 | 56 | {openPlayListModal && } 57 | 58 | {openUserModal && } 59 | 60 | {OpenSongToPlayListModal && } 61 | 62 | ); 63 | 64 | }; 65 | 66 | export default Admin; 67 | -------------------------------------------------------------------------------- /client/src/components/Admin/MyModals/AddAlbum.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import './MyModal.css'; 3 | import 'bootstrap/dist/css/bootstrap.min.css'; 4 | import { Modal, Button, DropdownButton, Dropdown } from 'react-bootstrap'; 5 | import { create, read } from '../../Network/Ajax'; 6 | 7 | function AddAlbum({ openModal, setOpenModal, formatDate }) { 8 | 9 | const [artistList, setArtistList] = useState([]) 10 | 11 | const [albumName, setAlbumName] = useState(''); 12 | const [albumArtist, setAlbumArtist] = useState(''); 13 | const [albumCreated, setAlbumCreated] = useState(''); 14 | const [albumImageLink, setAlbumImageLink] = useState(''); 15 | 16 | const getArtistsList = () => { 17 | read('/api/v1/artists') 18 | .then(setArtistList) 19 | .catch(console.error); 20 | } 21 | 22 | const sendNewAlbum = () => { 23 | const newAlbum = { 24 | name: albumName, 25 | artistId: albumArtist, 26 | createdAt: albumCreated, 27 | uploadAt: formatDate(new Date()), 28 | coverImg: albumImageLink 29 | }; 30 | create('/api/v1/albums', newAlbum) 31 | .then(handleClose) 32 | .catch(console.error) 33 | }; 34 | 35 | const handleClose = () => setOpenModal(false); 36 | 37 | return ( 38 | <> 39 | 47 | 48 | Add New Album 49 | 50 | 51 |
52 | 53 | setAlbumName(e.target.value)} required />
54 | 55 | {artistList.map((option) => 56 | {option.name} 57 | )} 58 | 59 | 60 | setAlbumCreated(e.target.value)} required />
61 | 62 | setAlbumImageLink(e.target.value)} required />
63 |
64 |
65 | 66 | 69 | 70 | 71 |
72 | 73 | ); 74 | }; 75 | 76 | export default AddAlbum; 77 | -------------------------------------------------------------------------------- /client/src/components/Admin/MyModals/AddArtist.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import './MyModal.css'; 3 | import 'bootstrap/dist/css/bootstrap.min.css'; 4 | import { Modal, Button } from 'react-bootstrap'; 5 | import { create } from '../../Network/Ajax'; 6 | 7 | 8 | function AddArtist({ openModal, setOpenModal, formatDate }) { 9 | 10 | const [artistName, setArtistName] = useState(''); 11 | const [artistCreated, setArtistCreated] = useState(''); 12 | const [artistImageLink, setArtistImageLink] = useState(''); 13 | 14 | const sendNewArtist = () => { 15 | const newArtist = { 16 | name: artistName, 17 | createdAt: artistCreated, 18 | uploadAt: formatDate(new Date()), 19 | coverImg: artistImageLink 20 | }; 21 | create('/api/v1/artists', newArtist) 22 | .then(handleClose) 23 | .catch(console.error) 24 | }; 25 | 26 | const handleClose = () => setOpenModal(false); 27 | 28 | return ( 29 | <> 30 | 38 | 39 | Add New Artist 40 | 41 | 42 |
43 | 44 | setArtistName(e.target.value)} required />
45 | 46 | setArtistCreated(e.target.value)} required />
47 | 48 | setArtistImageLink(e.target.value)} required />
49 |
50 |
51 | 52 | 55 | 56 | 57 |
58 | 59 | ); 60 | }; 61 | 62 | export default AddArtist; 63 | -------------------------------------------------------------------------------- /client/src/components/Admin/MyModals/AddPlayList.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import './MyModal.css'; 3 | import 'bootstrap/dist/css/bootstrap.min.css'; 4 | import { Modal, Button } from 'react-bootstrap'; 5 | import { create } from '../../Network/Ajax'; 6 | import { formatDate } from '../../Services/globalVariables' 7 | 8 | function AddPlayList({ openModal, setOpenModal }) { 9 | 10 | const [playListName, setPlayListName] = useState(''); 11 | const [playListCreated, setPlayListCreated] = useState(''); 12 | const [playListImageLink, setplayListImageLink] = useState(''); 13 | 14 | const sendNewPlayList = () => { 15 | const newPlayListObject = { 16 | name: playListName, 17 | createdAt: playListCreated, 18 | uploadAt: formatDate(new Date()), 19 | coverImg: playListImageLink 20 | }; 21 | 22 | create('/api/v1/playlists', newPlayListObject) 23 | .then((res) => 24 | create('/api/v1/interactions/playlists', { 25 | playlistId: res.id 26 | }) 27 | .then(handleClose) 28 | .catch(console.error)) 29 | .catch(console.error) 30 | }; 31 | 32 | const handleClose = () => setOpenModal(false); 33 | 34 | return ( 35 | <> 36 | 44 | 45 | Add New PlayList 46 | 47 | 48 |
49 | 50 | setPlayListName(e.target.value)} required />
51 | 52 | setPlayListCreated(e.target.value)} required />
53 | 54 | setplayListImageLink(e.target.value)} required />
55 |
56 |
57 | 58 | 61 | 62 | 63 |
64 | 65 | ); 66 | }; 67 | 68 | export default AddPlayList; 69 | -------------------------------------------------------------------------------- /client/src/components/Admin/MyModals/AddSong.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import './MyModal.css'; 3 | import 'bootstrap/dist/css/bootstrap.min.css'; 4 | import { Modal, Button } from 'react-bootstrap'; 5 | import { read, create } from '../../Network/Ajax'; 6 | import { DropdownButton, Dropdown } from 'react-bootstrap'; 7 | 8 | function AddSong({ openModal, setOpenModal, formatDate }) { 9 | 10 | const [artistList, setArtistList] = useState([]) 11 | const [albumsList, setAlbumsList] = useState([]) 12 | 13 | const [songName, setSongName] = useState(''); 14 | const [songArtist, setSongArtist] = useState(); 15 | const [songAlbum, setSongAlbum] = useState(''); 16 | const [songTrackNumber, setSongTrackNumber] = useState(''); 17 | const [songLength, setSongLength] = useState(''); 18 | const [songLycris, setSongLycris] = useState(''); 19 | const [songCreated, setSongCreated] = useState(''); 20 | const [songLink, setSongLink] = useState(''); 21 | 22 | const getArtistsList = () => { 23 | read('/api/v1/artists') 24 | .then(setArtistList) 25 | .catch(console.error); 26 | } 27 | 28 | const getAlbumsList = (artistID) => { 29 | read(`/api/v1/artists/byId/${artistID}`) 30 | .then((res) => { 31 | setAlbumsList(res.Albums) 32 | setSongArtist(artistID) 33 | }) 34 | .catch(console.error); 35 | } 36 | 37 | const getTrackNumber = (albumID) => { 38 | setSongAlbum(albumID) 39 | read(`/api/v1/albums/byId/${albumID}`) 40 | .then((res) => { 41 | setSongTrackNumber(Math.max(...res.Songs.map((album) => album.trackNumber)) + 1) 42 | console.log(Math.max(...res.Songs.map((album) => album.trackNumber)) + 1); 43 | }) 44 | .catch(console.error); 45 | } 46 | 47 | const sendNewSong = () => { 48 | const newSong = { 49 | name: songName, 50 | artistId: songArtist, 51 | albumId: songAlbum, 52 | trackNumber: songTrackNumber, 53 | length: songLength, 54 | lyrics: songLycris, 55 | createdAt: songCreated, 56 | uploadAt: formatDate(new Date()), 57 | youtubeLink: songLink 58 | }; 59 | create('/api/v1/songs', newSong) 60 | .then(handleClose) 61 | .catch(console.error); 62 | }; 63 | 64 | const handleClose = () => setOpenModal(false); 65 | 66 | return ( 67 | <> 68 | 76 | 77 | Add New Song 78 | 79 | 80 | 81 |
82 | 83 | setSongName(e.target.value)} required />
84 | 85 | 86 | {artistList.map((option) => 87 | {option.name} 88 | )} 89 | 90 | {songArtist && 91 | getAlbumsList(songArtist)} onSelect={getTrackNumber} > 92 | {albumsList.map((option) => 93 | {option.name} 94 | )} 95 | } 96 | 97 | setSongLength(e.target.value)} required />
98 | 99 | setSongLycris(e.target.value)} required />
100 | 101 | setSongCreated(e.target.value)} required />
102 | 103 | setSongLink(e.target.value)} required />
104 |
105 |
106 | 107 | 110 | 111 | 112 |
113 | 114 | ); 115 | }; 116 | 117 | export default AddSong; 118 | -------------------------------------------------------------------------------- /client/src/components/Admin/MyModals/AddSongToPlayList.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import './MyModal.css'; 3 | import 'bootstrap/dist/css/bootstrap.min.css'; 4 | import { Modal, Button } from 'react-bootstrap'; 5 | import { read, create } from '../../Network/Ajax'; 6 | import { DropdownButton, Dropdown } from 'react-bootstrap'; 7 | 8 | function AddSongToPlayList({ openModal, setOpenModal }) { 9 | 10 | const [playListList, setPlayListList] = useState([]) 11 | const [songsList, setSongsList] = useState([]) 12 | 13 | const [PlayList, setPlayList] = useState(); 14 | const [song, setSong] = useState(); 15 | 16 | 17 | const getPlaylistsList = () => { 18 | read(`/api/v1/playlists`) 19 | .then(setPlayListList) 20 | .catch(console.error) 21 | } 22 | 23 | const getSongsList = () => { 24 | read(`/api/v1/songs`) 25 | .then(setSongsList) 26 | .catch(console.error) 27 | } 28 | 29 | const handleClose = () => setOpenModal(false); 30 | 31 | const sendSongToPlayList = () => { 32 | const newConneaction = { 33 | songId: song, 34 | playlistId: PlayList 35 | } 36 | create('/api/v1/songsInPlaylists', newConneaction) 37 | .then(handleClose) 38 | .catch(console.error) 39 | } 40 | 41 | return ( 42 | <> 43 | 51 | 52 | Add New PlayList 53 | 54 | 55 | 56 | {playListList.map((option) => 57 | {option.name} 58 | )} 59 | 60 | 61 | {songsList.map((option) => 62 | {option.name} 63 | )} 64 | 65 | 66 | 67 | 70 | 71 | 72 | 73 | 74 | ); 75 | }; 76 | 77 | export default AddSongToPlayList; 78 | -------------------------------------------------------------------------------- /client/src/components/Admin/MyModals/AddUser.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import './MyModal.css'; 3 | import 'bootstrap/dist/css/bootstrap.min.css'; 4 | import { Modal, Button } from 'react-bootstrap'; 5 | import { create } from '../../Network/Ajax'; 6 | 7 | function AddUser({ openModal, setOpenModal, formatDate }) { 8 | 9 | const [userName, setUserName] = useState(''); 10 | const [userEmail, setUserEmail] = useState(''); 11 | const [userPassword, setUserPassword] = useState(''); 12 | const [userIsAdmin, setUserIsAdmin] = useState(false); 13 | const [userPreferences, setUserPreferences] = useState(''); 14 | const [userRememberToken, setUserRememberToken] = useState(false); 15 | const [userCreated, setUserCreated] = useState(''); 16 | 17 | const sendNewUser = () => { 18 | const newUser = { 19 | name: userName, 20 | email: userEmail, 21 | password: userPassword, 22 | isAdmin: userIsAdmin, 23 | preferences: userPreferences, 24 | rememberToken: userRememberToken, 25 | createdAt: userCreated, 26 | uploadAt: formatDate(new Date()) 27 | }; 28 | create('/api/v1/users', newUser) 29 | .then(handleClose) 30 | .catch(console.error) 31 | }; 32 | 33 | const handleClose = () => setOpenModal(false); 34 | 35 | return ( 36 | <> 37 | 45 | 46 | Add New User 47 | 48 | 49 |
50 | 51 | setUserName(e.target.value)} required />
52 | 53 | setUserPassword(e.target.value)} required />
54 | 55 | setUserEmail(e.target.value)} required />
56 | 57 | setUserIsAdmin(e.target.checked)} />
58 | 59 | setUserPreferences(e.target.value)} required />
60 | 61 | setUserRememberToken(e.target.checked)} />
62 | 63 | setUserCreated(e.target.value)} required />
64 |
65 |
66 | 67 | 70 | 71 | 72 |
73 | 74 | ); 75 | } 76 | 77 | export default AddUser; 78 | -------------------------------------------------------------------------------- /client/src/components/Admin/MyModals/MyModal.css: -------------------------------------------------------------------------------- 1 | .addNewModal { 2 | color: black; 3 | } -------------------------------------------------------------------------------- /client/src/components/Albums/Album.css: -------------------------------------------------------------------------------- 1 | .Album { 2 | display: flex; 3 | flex-direction: column; 4 | margin-bottom: 15px; 5 | border-bottom: 1px solid lightslategray; 6 | } 7 | 8 | .albumsImg { 9 | height: 309px; 10 | width: auto; 11 | } 12 | 13 | .AlbumContainer { 14 | display: flex; 15 | flex-direction: row; 16 | justify-content: space-around; 17 | margin-bottom: 20px; 18 | } 19 | 20 | .AlbumDescription { 21 | margin-top: 20px; 22 | margin-right: 200px; 23 | font-style: oblique; 24 | font-family: 'Courier New', Courier, monospace; 25 | display: grid; 26 | grid-template-rows: 2fr 1fr 1fr; 27 | text-align: left; 28 | } 29 | 30 | .AlbumName { 31 | font-size: 40px !important; 32 | } 33 | 34 | .globalLikeButtons { 35 | display: flex; 36 | flex-direction: row; 37 | justify-content: space-between; 38 | } -------------------------------------------------------------------------------- /client/src/components/Albums/Album.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { create } from '../Network/Ajax'; 3 | import 'bootstrap/dist/css/bootstrap.min.css'; 4 | import './Album.css'; 5 | import ListOfSongs from '../Songs/ListOfSongs'; 6 | import { Link } from 'react-router-dom'; 7 | import like from '../../images/like.png'; 8 | import likeActive from '../../images/likeActive.png' 9 | import dislike from '../../images/disLike.png'; 10 | import dislikeActive from '../../images/dislikeActive.png' 11 | 12 | function Album({ album, isLiked, albumDisplay = 'inline', artistDisplay = 'inline' }) { 13 | 14 | const [likeButtonSrc, setLikeButtonSrc] = useState(isLiked === true ? likeActive : like) 15 | const [disLikeButtonSrc, setDisLikeButtonSrc] = useState(isLiked === false ? dislikeActive : dislike) 16 | 17 | const handleLikeButton = (e) => { 18 | const newInteraction = { 19 | albumId: album.id, 20 | isLiked: true, 21 | } 22 | create('/api/v1/interactions/albums', newInteraction) 23 | .then(res => { 24 | setLikeButtonSrc(likeActive) 25 | setDisLikeButtonSrc(dislike) 26 | }) 27 | .catch(console.error); 28 | } 29 | 30 | const handleDisLikeButton = (e) => { 31 | const newInteraction = { 32 | albumId: album.id, 33 | isLiked: false, 34 | } 35 | create('/api/v1/interactions/albums', newInteraction) 36 | .then(res => { 37 | setDisLikeButtonSrc(dislikeActive) 38 | setLikeButtonSrc(like) 39 | }) 40 | .catch(console.error); 41 | } 42 | return ( 43 |
44 |
45 | {album.name} 46 |
47 | {album.name} 48 | {album.Artist.name} 49 | {album.createdAt &&
{new Date(album.createdAt).toDateString()}
} 50 |
51 | {''} 52 | {''} 53 |
54 |
55 |
56 |
57 | 58 |
59 |
60 | 61 | ) 62 | } 63 | 64 | export default Album; -------------------------------------------------------------------------------- /client/src/components/Albums/ListOfAlbums.css: -------------------------------------------------------------------------------- 1 | .album-list { 2 | margin-top: 100px; 3 | } 4 | 5 | .OneAlbum { 6 | margin-top: 100px; 7 | } 8 | 9 | .albumTitle { 10 | font-style: oblique; 11 | font-family: 'Courier New', Courier, monospace; 12 | } -------------------------------------------------------------------------------- /client/src/components/Albums/ListOfAlbums.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import './ListOfAlbums.css'; 3 | import { read } from '../Network/Ajax'; 4 | import NavBar from '../NavBar/NavBar'; 5 | import { useHistory } from 'react-router-dom'; 6 | import Album from './Album'; 7 | 8 | function ListOfAlbums() { 9 | 10 | const [albumsInteractions, setAlbumsInteractions] = useState([]) 11 | const [albumsList, setAlbumsList] = useState([]) 12 | const history = useHistory() 13 | useEffect(() => { 14 | read('/api/v1/albums') 15 | .then(res => setAlbumsList(res)) 16 | .catch(err => { 17 | if (err.status === 403) { 18 | history.push('/') 19 | } 20 | }) 21 | read('/api/v1/interactions/albums/userInteractions') 22 | .then(res => { 23 | setAlbumsInteractions(res) 24 | }) 25 | .catch(console.error) 26 | }, [history]); 27 | 28 | const listToPrint = albumsList.map((album, index) => { 29 | return ( 30 | < Album 31 | key={album.name + album.id} 32 | index={index} 33 | album={album} 34 | isLiked={albumsInteractions.map((element) => { 35 | if (element.albumId === album.id) { 36 | return element.isLiked 37 | } else return null; 38 | }).filter(function (el) { 39 | return el !== null; 40 | })[0]} 41 | /> 42 | ) 43 | }) 44 | 45 | return ( 46 | <> 47 | 48 |

Albums


49 |
    50 | {listToPrint} 51 |
52 | 53 | ) 54 | } 55 | 56 | export default ListOfAlbums; 57 | -------------------------------------------------------------------------------- /client/src/components/Albums/OneAlbum.css: -------------------------------------------------------------------------------- 1 | .OneAlbum { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .OneAlbumImage { 7 | height: 309px; 8 | width: 550px; 9 | } 10 | 11 | .OneAlbumContainer { 12 | display: flex; 13 | flex-direction: row; 14 | justify-content: space-around 15 | } 16 | 17 | .OneAlbumDescription { 18 | margin-top: 20px; 19 | font-style: oblique; 20 | font-family: 'Courier New', Courier, monospace; 21 | display: grid; 22 | grid-template-rows: 2fr 1fr 1fr; 23 | text-align: left; 24 | font-size: 30px; 25 | } 26 | 27 | .albumSongsList { 28 | margin-top: 50px; 29 | } -------------------------------------------------------------------------------- /client/src/components/Albums/OneAlbum.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import './OneAlbum.css'; 3 | import { read, create } from '../Network/Ajax'; 4 | import { useParams } from 'react-router-dom'; 5 | import ListOfSongs from '../Songs/ListOfSongs'; 6 | import NotFound from '../Services/NotFound'; 7 | import like from '../../images/like.png'; 8 | import likeActive from '../../images/likeActive.png' 9 | import dislike from '../../images/disLike.png'; 10 | import dislikeActive from '../../images/dislikeActive.png' 11 | 12 | function OneAlbum() { 13 | 14 | const { id } = useParams(); 15 | const [loading, setLoading] = useState(true); 16 | const [album, setAlbum] = useState(); 17 | const [songList, setSongsList] = useState([]); 18 | const [likeButtonSrc, setLikeButtonSrc] = useState(like) 19 | const [disLikeButtonSrc, setDisLikeButtonSrc] = useState(dislike) 20 | 21 | useEffect(() => { 22 | read(`/api/v1/albums/byId/${id}`) 23 | .then((res) => { 24 | setAlbum(res); 25 | setSongsList(res.Songs); 26 | setLoading(false); 27 | }) 28 | .catch(console.error) 29 | read('/api/v1/interactions/albums/userInteractions') 30 | .then(res => { 31 | switch (res.filter((album) => album.albumId === parseInt(id))[0].isLiked) { 32 | case true: 33 | setLikeButtonSrc(likeActive) 34 | break; 35 | case undefined: 36 | break; 37 | case false: 38 | setDisLikeButtonSrc(dislikeActive) 39 | break; 40 | default: 41 | break; 42 | } 43 | }) 44 | .catch(console.error) 45 | }, [id]); 46 | 47 | 48 | const handleLikeButton = (e) => { 49 | const newInteraction = { 50 | albumId: album.id, 51 | isLiked: true, 52 | } 53 | create('/api/v1/interactions/albums', newInteraction) 54 | .then(res => { 55 | setLikeButtonSrc(likeActive) 56 | setDisLikeButtonSrc(dislike) 57 | }) 58 | .catch(console.error); 59 | } 60 | 61 | const handleDisLikeButton = (e) => { 62 | const newInteraction = { 63 | albumId: album.id, 64 | isLiked: false, 65 | } 66 | create('/api/v1/interactions/albums', newInteraction) 67 | .then(res => { 68 | setDisLikeButtonSrc(dislikeActive) 69 | setLikeButtonSrc(like) 70 | }) 71 | .catch(console.error); 72 | } 73 | 74 | return ( 75 | album ? (
76 |
77 | {album.name} 78 |
79 |
Name: {album.name}
80 |
Artist: {album.Artist.name}
81 | {album.createdAt &&
created At: {new Date(album.createdAt).toDateString()}
} 82 | {album.uploadAt &&
upload At{new Date(album.uploadAt).toDateString()}
} 83 |
84 | {''} 85 | {''} 86 |
87 |
88 |
89 |
90 | 91 |
92 |
) 93 | : 94 | !loading ? 95 | 96 | :
97 | ); 98 | }; 99 | 100 | export default OneAlbum; 101 | -------------------------------------------------------------------------------- /client/src/components/Artists/Artist.css: -------------------------------------------------------------------------------- 1 | .Artist { 2 | display: flex; 3 | flex-direction: column; 4 | margin-bottom: 15px; 5 | border-bottom: 1px solid lightslategray; 6 | } 7 | 8 | .Artist-img { 9 | height: 309px; 10 | width: auto; 11 | } 12 | 13 | .artistTitle { 14 | font-style: oblique; 15 | font-family: 'Courier New', Courier, monospace; 16 | } 17 | 18 | .ArtistContainer { 19 | display: flex; 20 | flex-direction: row; 21 | justify-content: space-around; 22 | margin-bottom: 20px; 23 | } 24 | 25 | .ArtistDescription { 26 | margin-top: 20px; 27 | margin-right: 200px; 28 | font-style: oblique; 29 | font-family: 'Courier New', Courier, monospace; 30 | display: grid; 31 | grid-template-rows: 2fr 1fr 1fr; 32 | text-align: left; 33 | } 34 | 35 | .ArtistName { 36 | font-size: 40px !important; 37 | } 38 | 39 | .AlbumArtist { 40 | font-size: 40px !important; 41 | } 42 | 43 | .OneArtistAlbumCarusel { 44 | display: flex; 45 | flex-direction: column; 46 | } -------------------------------------------------------------------------------- /client/src/components/Artists/Artist.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { create } from '../Network/Ajax'; 3 | import 'bootstrap/dist/css/bootstrap.min.css'; 4 | import './Artist.css'; 5 | import ListOfSongs from '../Songs/ListOfSongs'; 6 | import { Link } from 'react-router-dom'; 7 | import like from '../../images/like.png'; 8 | import likeActive from '../../images/likeActive.png' 9 | import dislike from '../../images/disLike.png'; 10 | import dislikeActive from '../../images/dislikeActive.png' 11 | 12 | function Artist({ artist, isLiked }) { 13 | const [likeButtonSrc, setLikeButtonSrc] = useState(isLiked === true ? likeActive : like) 14 | const [disLikeButtonSrc, setDisLikeButtonSrc] = useState(isLiked === false ? dislikeActive : dislike) 15 | 16 | const handleLikeButton = (e) => { 17 | const newInteraction = { 18 | artistId: artist.id, 19 | isLiked: true, 20 | } 21 | create('/api/v1/interactions/artists', newInteraction) 22 | .then(res => { 23 | setLikeButtonSrc(likeActive) 24 | setDisLikeButtonSrc(dislike) 25 | }) 26 | .catch(console.error); 27 | } 28 | 29 | const handleDisLikeButton = (e) => { 30 | const newInteraction = { 31 | artistId: artist.id, 32 | isLiked: false, 33 | } 34 | create('/api/v1/interactions/artists', newInteraction) 35 | .then(res => { 36 | setDisLikeButtonSrc(dislikeActive) 37 | setLikeButtonSrc(like) 38 | }) 39 | .catch(console.error); 40 | } 41 | 42 | return ( 43 |
44 |
45 | {artist.name} 46 |
47 | {artist.name} 48 | {artist.createdAt &&
Created At: {new Date(artist.createdAt).toDateString()}
} 49 |
50 | {''} 51 | {''} 52 |
53 |
54 |
55 |
56 | 57 |
58 |
59 | 60 | ) 61 | } 62 | 63 | export default Artist; -------------------------------------------------------------------------------- /client/src/components/Artists/ListOfArtists.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import './Artist.css'; 3 | import { read } from '../Network/Ajax'; 4 | import { useHistory } from 'react-router-dom'; 5 | import NavBar from '../NavBar/NavBar'; 6 | import Artist from './Artist'; 7 | 8 | function ListOfArtists() { 9 | 10 | const [artistsInteractions, setAartistsInteractions] = useState([]) 11 | const [artistsList, setArtistsList] = useState([]) 12 | const history = useHistory() 13 | useEffect(() => { 14 | read('/api/v1/artists') 15 | .then(res => setArtistsList(res)) 16 | .catch(err => { 17 | if (err.status === 403) { 18 | history.push('/') 19 | } 20 | }) 21 | read('/api/v1/interactions/artists/userInteractions') 22 | .then(res => { 23 | setAartistsInteractions(res) 24 | }) 25 | .catch(console.error) 26 | }, [history]); 27 | 28 | const listToPrint = artistsList.map((artist, index) => { 29 | return ( 30 | < Artist 31 | index={index} 32 | key={artist.name + artist.id} 33 | artist={artist} 34 | isLiked={artistsInteractions.map((element) => { 35 | if (element.artistId === artist.id) { 36 | return element.isLiked 37 | } else return null; 38 | }).filter(function (el) { 39 | return el !== null; 40 | })[0]} 41 | /> 42 | ) 43 | }) 44 | 45 | return ( 46 | <> 47 | 48 |

Artists

49 |
    50 | {listToPrint} 51 |
52 | 53 | ) 54 | } 55 | 56 | export default ListOfArtists; 57 | -------------------------------------------------------------------------------- /client/src/components/Artists/OneArtist.css: -------------------------------------------------------------------------------- 1 | .OneArtist { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | /* .ArtistContainer { 7 | display: flex; 8 | flex-direction: row; 9 | justify-content: space-around; 10 | border-bottom: 1px solid white; 11 | } */ 12 | 13 | .OneArtitstDescriptionContainer { 14 | font-style: oblique; 15 | font-family: 'Courier New', Courier, monospace; 16 | text-align: left; 17 | font-size: 40px; 18 | } 19 | 20 | .artistImage { 21 | height: 350px; 22 | width: 550px; 23 | } 24 | 25 | .OneArtitstDescription { 26 | margin-top: 20px; 27 | font-size: 20px; 28 | max-width: 400px; 29 | } 30 | 31 | .OneArtistSongList { 32 | display: flex; 33 | flex-direction: row; 34 | } 35 | 36 | .OneArtistAlbums { 37 | max-width: 600px; 38 | margin-top: 40px; 39 | } 40 | 41 | .artisrcreatedAt { 42 | margin-top: 40px; 43 | font-size: 20px; 44 | } -------------------------------------------------------------------------------- /client/src/components/Artists/OneArtist.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import './OneArtist.css'; 3 | import { Link } from 'react-router-dom'; 4 | import { read, create } from '../Network/Ajax'; 5 | import { useParams } from 'react-router-dom'; 6 | import Carousel from 'react-elastic-carousel'; 7 | import ElementToCarusel from '../Home/ElementToCarusel'; 8 | import NotFound from '../Services/NotFound'; 9 | import { GetYTId, breakPoints } from '../Services/globalVariables'; 10 | import like from '../../images/like.png'; 11 | import likeActive from '../../images/likeActive.png' 12 | import dislike from '../../images/disLike.png'; 13 | import dislikeActive from '../../images/dislikeActive.png' 14 | 15 | function OneArtist() { 16 | 17 | const { id } = useParams(); 18 | const [loading, setLoading] = useState(true); 19 | const [artist, setArtist] = useState(); 20 | const [songList, setSongsList] = useState([]); 21 | const [albums, setAlbums] = useState([]); 22 | const [finish, setFinish] = useState(false) 23 | const [likeButtonSrc, setLikeButtonSrc] = useState(like) 24 | const [disLikeButtonSrc, setDisLikeButtonSrc] = useState(dislike) 25 | 26 | useEffect(() => { 27 | read(`/api/v1/artists/byId/${id}`) 28 | .then((res) => { 29 | setArtist(res); 30 | setSongsList(res.Songs); 31 | setAlbums(res.Albums) 32 | setLoading(false) 33 | setFinish(true) 34 | }).catch(console.error) 35 | read('/api/v1/interactions/artists/userInteractions') 36 | .then(res => { 37 | switch (res.filter((artist) => { 38 | return artist.artistId === parseInt(id) 39 | })[0].isLiked) { 40 | case true: 41 | setLikeButtonSrc(likeActive) 42 | break; 43 | case undefined: 44 | break; 45 | case false: 46 | setDisLikeButtonSrc(dislikeActive) 47 | break; 48 | default: 49 | break; 50 | } 51 | }) 52 | .catch(console.error) 53 | }, [id]); 54 | 55 | 56 | 57 | const handleLikeButton = (e) => { 58 | const newInteraction = { 59 | artistId: artist.id, 60 | isLiked: true, 61 | } 62 | create('/api/v1/interactions/artists', newInteraction) 63 | .then(res => { 64 | setLikeButtonSrc(likeActive) 65 | setDisLikeButtonSrc(dislike) 66 | }) 67 | .catch(console.error); 68 | } 69 | 70 | const handleDisLikeButton = (e) => { 71 | const newInteraction = { 72 | artistId: artist.id, 73 | isLiked: false, 74 | } 75 | create('/api/v1/interactions/artists', newInteraction) 76 | .then(res => { 77 | setDisLikeButtonSrc(dislikeActive) 78 | setLikeButtonSrc(like) 79 | }) 80 | .catch(console.error); 81 | } 82 | 83 | return ( 84 | finish ? 85 |
86 |
87 | {artist.name} 88 |
89 |
{artist.name}
90 |
About: {artist.description}
91 | {artist.updatedAt &&
createdAt: {new Date(artist.updatedAt).toDateString()}
} 92 |
93 | {''} 94 | {''} 95 |
96 |
97 |
98 | 99 |
100 | 101 | {albums.map((album) => 102 | 103 | )} 104 | 105 |
    106 | {songList.map((song, index) => ( 107 |
  1. 108 | 109 | {''} 110 | 111 |
    {song.name}
    112 | {albums[0].name}
    113 | {artist.name} 114 |
    115 | 116 |
  2. 117 | ))} 118 |
119 |
120 |
121 | : 122 | !loading ? 123 | 124 | :
125 | ) 126 | 127 | } 128 | 129 | export default OneArtist; 130 | -------------------------------------------------------------------------------- /client/src/components/Home/ElementToCarusel.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import 'bootstrap/dist/css/bootstrap.min.css'; 4 | import { GetYTId } from '../Services/globalVariables'; 5 | 6 | function ElementToCarusel({ query, element, artist = false, border = '0px', widthPic = 'auto' }) { 7 | 8 | let queryExist = query.path === "song" ? `?${artist ? 'artist' : query.path}=${query.id}` : '' 9 | 10 | let imgSrc = query.path === "song" ? `https://img.youtube.com/vi/${GetYTId(element.youtubeLink)}/0.jpg` : element.coverImg 11 | 12 | return ( 13 |
  • 14 | 15 | {' 16 |
    {element.name}
    17 | 18 |
  • 19 | ) 20 | } 21 | 22 | export default ElementToCarusel; 23 | -------------------------------------------------------------------------------- /client/src/components/Home/Home.css: -------------------------------------------------------------------------------- 1 | .Home { 2 | display: flex; 3 | flex-direction: column; 4 | width: 100%; 5 | height: 100%; 6 | } 7 | 8 | .ElementToCarusel { 9 | list-style: none; 10 | } 11 | 12 | .listTitle { 13 | font-style: oblique; 14 | font-family: 'Courier New', Courier, monospace; 15 | /* font-family: YT Sans, Roboto, Noto Naskh Arabic UI, Arial, sans-serif; */ 16 | /* font-weight: 700; */ 17 | /* font-variant: small-caps; */ 18 | font-size: 30px; 19 | } 20 | -------------------------------------------------------------------------------- /client/src/components/Home/Home.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import './Home.css'; 3 | import NavBar from '../NavBar/NavBar'; 4 | import { read } from '../Network/Ajax'; 5 | import Carousel from 'react-elastic-carousel'; 6 | import ElementToCarusel from './ElementToCarusel'; 7 | import { breakPoints } from '../Services/globalVariables'; 8 | 9 | function Home() { 10 | 11 | const [songList, setSongsList] = useState([]) 12 | const [albums, setAlbums] = useState([]); 13 | const [artists, setArtists] = useState([]); 14 | const [playlists, setPlaylists] = useState([]); 15 | 16 | useEffect(() => { 17 | read('/api/v1/songs/top') 18 | .then(res => setSongsList(res)) 19 | .catch(console.error) 20 | read('/api/v1/albums/top') 21 | .then(res => setAlbums(res)) 22 | .catch(console.error) 23 | read('/api/v1/artists/top') 24 | .then(res => setArtists(res)) 25 | .catch(console.error) 26 | read('/api/v1/interactions/playlists/all') 27 | .then(res => setPlaylists(res.map(playlist => 28 | playlist.Playlist 29 | ))) 30 | .catch(console.error) 31 | // eslint-disable-next-line 32 | }, []); 33 | 34 | return ( 35 | <> 36 | 37 |

    Top Songs

    38 | 39 | {songList.map((song) => ( 40 | 41 | ))} 42 | 43 |

    44 |

    Top Albums

    45 | 46 | {albums.map((album) => ( 47 | 48 | ))} 49 | 50 |

    51 |

    Top Artists

    52 | 53 | {artists.map((artist) => ( 54 | 55 | ))} 56 | 57 |

    58 |

    Top Playlists

    59 | 60 | {playlists.map((playlist) => ( 61 | 62 | ))} 63 | 64 | 65 | ) 66 | } 67 | 68 | export default Home; 69 | -------------------------------------------------------------------------------- /client/src/components/LogIn/Login.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react'; 2 | import { useForm } from 'react-hook-form'; 3 | import { Link } from 'react-router-dom'; 4 | import { create } from '../Network/Ajax'; 5 | import { Logged } from '../Services/useContextComp'; 6 | import './forms.css'; 7 | import { Modal } from 'react-bootstrap'; 8 | import Hilarious from '../../images/Hilarious.gif'; 9 | import userName from '../../images/userName.png'; 10 | import admin from '../../images/admin.png'; 11 | 12 | function LogIn() { 13 | const { register: logIn, handleSubmit, errors } = useForm(); 14 | const [error, setError] = useState() 15 | const value = useContext(Logged); 16 | const onSubmit = (data) => { 17 | create('/users/logIn', data) 18 | .then(res => { 19 | value.setIsLogged(true); 20 | }) 21 | .catch(e => { 22 | setError(e.message) 23 | console.error(e.message) 24 | }) 25 | }; 26 | const [openModal, setOpenModal] = useState(false); 27 | const handleClose = () => setOpenModal(false); 28 | 29 | return ( 30 |
    31 | 32 | 33 | Ha Ha you Forgot Your Password 34 | 35 | 36 | {' 37 | 38 | 39 |
    40 |
    41 |
    42 |
    43 |

    Sign In

    44 |
    45 | 46 | 47 | 48 |
    49 |
    50 |
    51 |
    52 |
    53 |
    54 | user 55 |
    56 | ()\]\\.,;:\s@"]+(\.[^<>()\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ })} /> 57 |
    58 |
    59 |
    60 | user 61 |
    62 | 63 | 64 |
    65 |
    66 | Remember Me 67 |
    68 |
    69 | {errors.email && 'Email is required.'}
    70 | {errors.password && 'Password is required.'} 71 | {error} 72 | 73 |
    74 |
    75 |
    76 |
    77 |
    78 | Don't have an account?
    Sign Up
    79 |
    80 |
    81 |
    setOpenModal(true)} >Forgot your password?
    82 |
    83 |
    84 |
    85 |
    86 |
    87 |
    88 | ); 89 | } 90 | 91 | export default LogIn; 92 | -------------------------------------------------------------------------------- /client/src/components/LogIn/forms.css: -------------------------------------------------------------------------------- 1 | .enteryForm { 2 | margin: 0px 50px 0px 0px; 3 | margin-top: -60px; 4 | width: 100%; 5 | height: 100vh; 6 | background-image: url('http://getwallpapers.com/wallpaper/full/a/5/d/544750.jpg'); 7 | background-size: cover; 8 | background-repeat: no-repeat; 9 | font-family: 'Numans', sans-serif; 10 | } 11 | 12 | .forgetPassword { 13 | cursor: pointer; 14 | } 15 | 16 | .modalForget { 17 | color: black; 18 | } 19 | 20 | .forgetPassword:hover { 21 | text-decoration: underline; 22 | } 23 | 24 | .container{ 25 | height: 100%; 26 | align-content: center; 27 | } 28 | 29 | .card{ 30 | height: 370px; 31 | margin-top: auto; 32 | margin-top: 15%; 33 | margin-bottom: auto; 34 | width: 400px; 35 | background-color: rgba(0,0,0,0.5) !important; 36 | } 37 | 38 | .socialIcon span{ 39 | font-size: 60px; 40 | margin-left: 10px; 41 | color: #FFC312; 42 | } 43 | 44 | .socialIcon span:hover{ 45 | color: white; 46 | cursor: pointer; 47 | } 48 | 49 | .card-header h3{ 50 | color: white; 51 | } 52 | 53 | .socialIcon{ 54 | position: absolute; 55 | right: 20px; 56 | top: -45px; 57 | } 58 | 59 | .input-group-prepend span{ 60 | width: 50px; 61 | background-color: #FFC312; 62 | color: black; 63 | border:0 !important; 64 | } 65 | 66 | input:focus{ 67 | outline: 0 0 0 0 !important; 68 | box-shadow: 0 0 0 0 !important; 69 | 70 | } 71 | 72 | .remember{ 73 | color: white; 74 | } 75 | 76 | .remember input 77 | { 78 | width: 20px; 79 | height: 20px; 80 | margin-left: 15px; 81 | margin-right: 5px; 82 | } 83 | 84 | .loginBtn{ 85 | color: black; 86 | background-color: #FFC312; 87 | width: 100px; 88 | } 89 | 90 | .loginBtn:hover{ 91 | color: black; 92 | background-color: white; 93 | } 94 | 95 | .links{ 96 | color: white; 97 | } 98 | 99 | .links a{ 100 | margin-left: 4px; 101 | } 102 | 103 | .email { 104 | font-size: 18px; 105 | font-weight: bold; 106 | } 107 | -------------------------------------------------------------------------------- /client/src/components/LogIn/registaer.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useForm } from 'react-hook-form'; 3 | import { Link, useHistory } from 'react-router-dom'; 4 | import { create } from '../Network/Ajax'; 5 | import userName from '../../images/userName.png'; 6 | import admin from '../../images/admin.png'; 7 | 8 | function LogIn() { 9 | 10 | const { register, handleSubmit, errors } = useForm(); 11 | const [error, setError] = useState() 12 | 13 | const location = useHistory() 14 | 15 | const onSubmit = (data) => { 16 | create('/users/register', data) 17 | .then(res => { 18 | location.push('/') 19 | } 20 | ) 21 | .catch(e => { 22 | setError(e.message) 23 | console.error(e.message) 24 | }) 25 | 26 | }; 27 | 28 | return ( 29 |
    30 |
    31 |
    32 |
    33 |
    34 |

    Sign Up

    35 |
    36 |
    37 |
    38 |
    39 |
    40 | user 41 |
    42 | 43 |
    44 |
    45 |
    46 | @ 47 |
    48 | ()\]\\.,;:\s@"]+(\.[^<>()\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ })} /> 49 | 50 |
    51 |
    52 |
    53 | user 54 |
    55 | 56 | 57 |
    58 |
    59 | Remember Me 60 |
    61 |
    62 | {error} {errors.name && 'name is required.'} {errors.email && 'Email is required.'} {errors.password && 'Password is required.'} 63 | 64 |
    65 |
    66 |
    67 |
    68 |
    69 | Already have an account?
    Sign In
    70 |
    71 |
    72 |
    73 |
    74 |
    75 |
    76 | ); 77 | } 78 | 79 | export default LogIn; 80 | -------------------------------------------------------------------------------- /client/src/components/MyLibrary/MyLibrary.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/components/MyLibrary/MyLibrary.css -------------------------------------------------------------------------------- /client/src/components/MyLibrary/MyLibrary.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { create, read } from '../Network/Ajax'; 3 | import Cookies from 'js-cookie'; 4 | import NavBar from '../NavBar/NavBar'; 5 | import Carousel from 'react-elastic-carousel'; 6 | import ElementToCarusel from '../Home/ElementToCarusel'; 7 | import { breakPoints } from '../Services/globalVariables'; 8 | import { Link } from 'react-router-dom'; 9 | 10 | const MyLibrary = () => { 11 | const [songList, setSongsList] = useState([]) 12 | const [albums, setAlbums] = useState([]); 13 | const [artists, setArtists] = useState([]); 14 | const [playlists, setPlaylists] = useState([]); 15 | 16 | useEffect(() => { 17 | read(`/api/v1/interactions/songs/byUser`) 18 | .then(res => { 19 | create('/api/v1/interactions/songs/songsByUser', [...new Set(res.map((inter => { 20 | if (inter.isLiked === true) { 21 | return inter.songId 22 | } else { return null } 23 | })).filter(function (el) { 24 | return el != null; 25 | }))]) 26 | .then(setSongsList) 27 | .catch(console.error) 28 | }) 29 | .catch(console.error) 30 | read('/api/v1/interactions/albums/userInteractions') 31 | .then(res => { 32 | create('/api/v1/interactions/albums/byUser', [...new Set(res.map((inter => { 33 | if (inter.isLiked === true) { 34 | return inter.albumId 35 | } else { return null } 36 | })).filter(function (el) { 37 | return el != null; 38 | }))]) 39 | .then(res => { 40 | setAlbums(res.map(album => 41 | album.Album 42 | )) 43 | }) 44 | }) 45 | .catch(console.error) 46 | .catch(console.error) 47 | read('/api/v1/interactions/artists/userInteractions') 48 | .then(res => { 49 | create('/api/v1/interactions/artists/byUser', [...new Set(res.map((inter => { 50 | if (inter.isLiked === true) { 51 | return inter.artistId 52 | } else { return null } 53 | })).filter(function (el) { 54 | return el != null; 55 | }))]) 56 | .then(res => setArtists(res.map(artist => 57 | artist.Artist 58 | ))) 59 | }) 60 | .catch(console.error) 61 | .catch(console.error) 62 | read('/api/v1/interactions/playlists/byUser') 63 | .then(res => setPlaylists(res.map(playlist => 64 | playlist.Playlist 65 | ))) 66 | .catch(console.error) 67 | }, []) 68 | 69 | return ( 70 | <> 71 | 72 |

    This is your personal page {Cookies.get('name')}

    73 |
    74 |

    This is your favorit songs

    75 | {songList.length > 0 ? 76 | {songList.map((song) => ( 77 | 78 | ))} 79 | 80 | :
    You don't have favorit songs yet, press 81 | here {' '} 82 | to add
    83 | } 84 |

    This is your favorit albums

    85 | {albums.length > 0 ? 86 | {albums.map((album) => ( 87 | 88 | ))} 89 | 90 | :
    You don't have favorit albums yet, press 91 | here {' '} 92 | to add
    93 | } 94 |

    95 |

    This is your favorit artists

    96 | {artists.length > 0 ? 97 | {artists.map((artist) => ( 98 | 99 | ))} 100 | 101 | :
    You don't have favorit albums yet, press 102 | here {' '} 103 | to add
    104 | } 105 |

    106 |

    This is your playlists

    107 | {playlists.length > 0 ? 108 | {playlists.map((playlist) => ( 109 | 110 | ))} 111 | 112 | :
    You don't have your own playlists yet, press 113 | here {' '} 114 | to add
    115 | } 116 | 117 | ) 118 | } 119 | 120 | export default MyLibrary; -------------------------------------------------------------------------------- /client/src/components/NavBar/NavBar.css: -------------------------------------------------------------------------------- 1 | .navbar-dark .navbar-brand>a { 2 | color: rgb(0, 179, 9) !important; 3 | } 4 | 5 | a { 6 | color: #fff !important; 7 | } 8 | 9 | .dropdown-item { 10 | color: black !important; 11 | } 12 | 13 | .dropdown-item:hover { 14 | color: white !important; 15 | background-color: #8D9093 !important; 16 | } -------------------------------------------------------------------------------- /client/src/components/Network/Ajax.js: -------------------------------------------------------------------------------- 1 | import Network from './Network'; 2 | 3 | export function read(endPoint) { 4 | return Network.get(endPoint); 5 | } 6 | 7 | export function create(endPoint, body = {}) { 8 | return Network.post(endPoint, { body }); 9 | } 10 | 11 | export function update(endPoint, body = {}) { 12 | return Network.put(endPoint, { body }); 13 | } 14 | 15 | export function remove(endPoint) { 16 | return Network.delete(endPoint); 17 | } 18 | -------------------------------------------------------------------------------- /client/src/components/Network/Network.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie'; 2 | import { removeTokents } from '../Services/globalVariables'; 3 | 4 | function Network(endPoint, { body, ...customConfig } = {}) { 5 | 6 | const headers = { 7 | "Content-Type": "application/json;charset=utf-8'", 8 | "Authorization": `${Cookies.get('token')}` 9 | }; 10 | 11 | const url = `${endPoint}` 12 | 13 | const config = { 14 | method: body ? "POST" : "GET", 15 | ...customConfig, 16 | headers: { 17 | ...headers, 18 | ...customConfig.headers, 19 | }, 20 | ...(body ? { body: JSON.stringify(body) } : {}), 21 | }; 22 | 23 | // console.log(`Sending ${config.method} to ${url} with data:`, body); 24 | 25 | return fetch(url, config).then(async (response, reject) => { 26 | const data = await response.json(); 27 | if (response.ok) { 28 | // console.log(`Got response ${response.status}`, data); 29 | return data 30 | } 31 | // else if (response.status === 403) { 32 | // removeTokents() 33 | // return window.location.assign('/') 34 | // } 35 | else { 36 | console.error(`${response.status} : '${data.message}'`); 37 | throw data 38 | } 39 | }); 40 | } 41 | 42 | Network.get = (endPoint) => Network(endPoint, { method: "GET" }); 43 | Network.post = (endPoint, body) => Network(endPoint, { method: "POST", ...body }); 44 | Network.put = (endPoint, body) => Network(endPoint, { method: "PUT", ...body }); 45 | Network.delete = (endPoint) => Network(endPoint, { method: "DELETE" }); 46 | 47 | export default Network; 48 | -------------------------------------------------------------------------------- /client/src/components/Playlists/ListOfPlaylists.css: -------------------------------------------------------------------------------- 1 | .playlistTitle { 2 | font-style: oblique; 3 | font-family: 'Courier New', Courier, monospace; 4 | } 5 | 6 | /* .OnePlayList { 7 | display: flex; 8 | flex-direction: column; 9 | } 10 | 11 | .OnePlayListContainer { 12 | display: flex; 13 | flex-direction: row; 14 | justify-content: space-around 15 | } 16 | 17 | .OnePlayListDescription { 18 | text-align: left; 19 | font-size: 30px; 20 | } */ 21 | 22 | -------------------------------------------------------------------------------- /client/src/components/Playlists/ListOfPlaylists.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import './ListOfPlaylists.css'; 3 | import { read } from '../Network/Ajax'; 4 | import { useHistory } from 'react-router-dom'; 5 | import NavBar from '../NavBar/NavBar'; 6 | import PlayList from './PlayList'; 7 | 8 | function ListOfPlaylists() { 9 | 10 | const [playListsList, setPlayListsList] = useState([]) 11 | const history = useHistory() 12 | useEffect(() => { 13 | read('/api/v1/interactions/playlists/all') 14 | .then(res => setPlayListsList(res.map(playlist=> 15 | playlist.Playlist 16 | ))) 17 | .catch(console.error) 18 | }, [history]); 19 | 20 | const listToPrint = playListsList.map((playlist, index) => { 21 | return ( 22 | < PlayList key={playlist.name + playlist.id} index={index} playlist={playlist}/> 23 | ) 24 | }) 25 | 26 | return ( 27 | <> 28 | 29 |

    PlayLists

    30 |
      31 | {listToPrint} 32 |
    33 | 34 | ) 35 | } 36 | 37 | export default ListOfPlaylists; 38 | -------------------------------------------------------------------------------- /client/src/components/Playlists/OnePlaylist.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import './ListOfPlaylists.css'; 3 | import { read } from '../Network/Ajax'; 4 | import { useParams } from 'react-router-dom'; 5 | import ListOfSongs from '../Songs/ListOfSongs'; 6 | import NotFound from '../Services/NotFound'; 7 | 8 | function OnePlaylist() { 9 | 10 | const { id } = useParams(); 11 | const [loading, setLoading] = useState(true); 12 | const [playList, setPlaylist] = useState(); 13 | const [songList, setSongsList] = useState([]); 14 | const SongsToPrint = songList.map((song) => song.Song) 15 | 16 | useEffect(() => { 17 | read(`/api/v1/playlists/byId/${id}`) 18 | .then((res) => { 19 | setPlaylist(res); 20 | setSongsList(res.PlaylistsSongs); 21 | setLoading(false); 22 | }) 23 | .catch(console.error) 24 | }, [id]); 25 | 26 | return ( 27 | playList ? 28 |
    29 |
    30 | {playList.name} 31 |
    32 |
    Name: {playList.name}
    33 |
    created at: {new Date(playList.createdAt).toDateString()}
    34 |
    upload at{new Date(playList.uploadedAt).toDateString()}
    35 |
    36 |
    37 | 38 |
    39 | : 40 | !loading ? 41 | 42 | :
    43 | ) 44 | 45 | } 46 | 47 | export default OnePlaylist; 48 | -------------------------------------------------------------------------------- /client/src/components/Playlists/PlayList.css: -------------------------------------------------------------------------------- 1 | .PlayList { 2 | display: flex; 3 | flex-direction: column; 4 | margin-bottom: 15px; 5 | border-bottom: 1px solid lightslategray; 6 | } 7 | 8 | .playListImage { 9 | height: 309px; 10 | width: 550px; 11 | } 12 | 13 | .PlayListContainer { 14 | display: flex; 15 | flex-direction: row; 16 | justify-content: space-around; 17 | margin-bottom: 20px; 18 | } 19 | 20 | .PlayListDescription { 21 | margin-top: 20px; 22 | margin-right: 200px; 23 | font-style: oblique; 24 | font-family: 'Courier New', Courier, monospace; 25 | display: grid; 26 | grid-template-rows: 2fr 1fr 1fr; 27 | text-align: left; 28 | } 29 | 30 | .PlayListName { 31 | font-size: 40px !important; 32 | } 33 | 34 | /* .PlayListSongsList { 35 | float: right; 36 | width: 700px; 37 | height: 200px; 38 | overflow: auto; 39 | } */ -------------------------------------------------------------------------------- /client/src/components/Playlists/PlayList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import 'bootstrap/dist/css/bootstrap.min.css'; 3 | import './PlayList.css'; 4 | import ListOfSongs from '../Songs/ListOfSongs'; 5 | import { Link } from 'react-router-dom'; 6 | 7 | function PlayList({ playlist }) { 8 | 9 | return ( 10 |
    11 |
    12 | {playlist.name} 13 |
    14 | {playlist.name} 15 |
    Created At: {new Date(playlist.createdAt).toDateString()}
    16 |
    Upload At{new Date(playlist.updatedAt).toDateString()}
    17 |
    18 |
    19 |
    20 | song.Song)} /> 21 |
    22 |
    23 | 24 | ) 25 | } 26 | 27 | export default PlayList; 28 | -------------------------------------------------------------------------------- /client/src/components/ReadMore/ReadMore.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | function ReadMore({ content, maxChar, color }) { 4 | const [isSliced, setIsSliced] = useState(content.length <= maxChar ? false : true); 5 | 6 | const text = isSliced ? content.slice(0, maxChar) : content; 7 | 8 | return ( 9 | <> 10 | {isSliced 11 | ? ( 12 |
    13 |

    14 | Lyrics: {(' ')} {text} 15 | ... 16 |

    17 | 18 |
    19 | ) 20 | : ( 21 |
    22 |

    23 | Lyrics: {(' ')} {text} 24 | {!(content.length <= maxChar) 25 | && ( 26 | 27 | )} 28 |

    29 |
    30 | )} 31 | 32 | ); 33 | } 34 | 35 | export default ReadMore; 36 | -------------------------------------------------------------------------------- /client/src/components/Services/Loading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | const NotFound = () => 5 |
    6 |

    404 page not found

    7 |

    We are sorry but the page you are looking for does not exist.

    8 |
    Go Home
    9 |  10 |
    11 | 12 | export default NotFound; -------------------------------------------------------------------------------- /client/src/components/Services/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link, useHistory } from 'react-router-dom'; 3 | import pageNotFound from '../../images/pageNotFound.png'; 4 | 5 | const NotFound = () => { 6 | 7 | const location = useHistory() 8 | 9 | return ( 10 |
    11 |

    404 page not found

    12 |

    We are sorry but the page you are looking for does not exist.

    13 | 14 |
    Go Home
    15 |  16 |
    17 | ) 18 | } 19 | 20 | export default NotFound; -------------------------------------------------------------------------------- /client/src/components/Services/globalVariables.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie'; 2 | 3 | export function GetYTId(songId) { 4 | if (songId) { 5 | let videoId = songId.split("v=")[1]; 6 | if (videoId) { 7 | 8 | const ampersandPosition = videoId.indexOf("&"); 9 | if (ampersandPosition !== -1) { 10 | videoId = videoId.substring(0, ampersandPosition); 11 | } 12 | return videoId 13 | } else { return '' } 14 | } else { return '' } 15 | } 16 | 17 | export const breakPoints = [ 18 | { width: 1, itemsToShow: 1 }, 19 | { width: 450, itemsToShow: 2 }, 20 | { width: 700, itemsToShow: 3 }, 21 | { width: 1000, itemsToShow: 4 }, 22 | { width: 1200, itemsToShow: 5 }, 23 | ] 24 | 25 | export function removeTokents() { 26 | Cookies.remove('name') 27 | Cookies.remove('token') 28 | Cookies.remove('isAdmin') 29 | Cookies.remove('user') 30 | } 31 | 32 | export function formatDate(date) { 33 | function pad(num) { return ('00' + num).slice(-2) }; 34 | 35 | // Change the date to SQL date format 36 | let dateStr = date.getUTCFullYear() + '-' + 37 | pad(date.getUTCMonth() + 1) + '-' + 38 | pad(date.getUTCDate() + 1) 39 | return dateStr; 40 | }; 41 | 42 | -------------------------------------------------------------------------------- /client/src/components/Services/share.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import shareButton from '../../images/shareButton.png'; 3 | import { OverlayTrigger, Popover } from 'react-bootstrap'; 4 | import { 5 | WhatsappShareButton, FacebookMessengerIcon, EmailShareButton, FacebookMessengerShareButton, 6 | WhatsappIcon, EmailIcon, FacebookShareButton, 7 | FacebookIcon, 8 | 9 | } from "react-share" 10 | 11 | export default function Share({ link, songName, artistName }) { 12 | const [anchorEl, setAnchorEl] = useState(null); 13 | 14 | const handleClick = (event) => { 15 | setAnchorEl(event.currentTarget); 16 | }; 17 | 18 | const open = Boolean(anchorEl); 19 | const id = open ? 'simple-popover' : undefined; 20 | 21 | return ( 22 |
    23 | 28 | 29 | 34 | 35 | 36 | 40 | 41 | 42 | 46 | 47 | 48 | 52 | 53 | 54 | 55 | 56 | } 57 | > 58 | {''} 59 | 60 |
    61 | ); 62 | } -------------------------------------------------------------------------------- /client/src/components/Services/useContextComp.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export const Logged = createContext(false); 4 | -------------------------------------------------------------------------------- /client/src/components/SideBar/SideBar.css: -------------------------------------------------------------------------------- 1 | .sidebar-wrapper{ 2 | position: fixed; 3 | margin-top: 55px; 4 | top: 0; 5 | height: 100vh; 6 | width: 200px; 7 | left: -1000px; 8 | background-color: #1A1D20; 9 | transition: 400ms; 10 | } 11 | 12 | .sidebar-wrapper-toggeled { 13 | z-index: 1000; 14 | position: fixed; 15 | margin-top: 55px; 16 | top: 0; 17 | left: 0; 18 | width: 200px; 19 | height: 100vh; 20 | background-color: #1A1D20; 21 | transition: 400ms; 22 | } 23 | 24 | .sidebar-heading { 25 | font-size: 25px; 26 | } 27 | 28 | .refButton { 29 | display: flex; 30 | flex-direction: row; 31 | justify-content: space-between; 32 | padding: .9rem 1rem; 33 | border-block-end: 1px solid #343A40; 34 | width: 100%; 35 | } 36 | 37 | .refButton:hover { 38 | padding: .9rem 1rem; 39 | background-color: #8D9093; 40 | width: 100%; 41 | } 42 | 43 | .nav { 44 | text-align: left; 45 | font-size: 18px; 46 | width: 205px; 47 | margin-top: 2px; 48 | margin-left: -20px; 49 | } 50 | 51 | .negetiveColor { 52 | filter: invert(100%) 53 | } 54 | -------------------------------------------------------------------------------- /client/src/components/SideBar/SideBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './SideBar.css'; 3 | import { Container, Row, Col, Nav } from "react-bootstrap"; 4 | import { Link } from 'react-router-dom'; 5 | import Cookies from 'js-cookie'; 6 | import home from '../../images/home.png'; 7 | import album from '../../images/album.png'; 8 | import artist from '../../images/artist.png'; 9 | import playlist from '../../images/playlist.png'; 10 | import myLibrary from '../../images/myLibrary.png'; 11 | import admin from '../../images/admin.png'; 12 | 13 | function SideBar({ menuClass, ToggleMenu }) { 14 | 15 | return ( 16 | 17 | 18 | 19 |
    Menu
    20 | 34 | 35 |
    36 |
    37 | ) 38 | } 39 | 40 | export default SideBar; 41 | -------------------------------------------------------------------------------- /client/src/components/Songs/ListOfSongs.css: -------------------------------------------------------------------------------- 1 | .songNameGlobal { 2 | margin-top: 20px; 3 | list-style: none; 4 | width: 170px; 5 | height: 250px; 6 | border: 1px solid lightslategray; 7 | border-radius: 10px; 8 | background-color: rgb(46, 52, 58); 9 | } 10 | -------------------------------------------------------------------------------- /client/src/components/Songs/ListOfSongs.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import 'bootstrap/dist/css/bootstrap.min.css'; 3 | import Song from './Song'; 4 | import './ListOfSongs.css'; 5 | import Carousel from 'react-elastic-carousel'; 6 | import { breakPoints } from '../Services/globalVariables'; 7 | 8 | function ListOfSongs({ query, songList, albumDisplay = 'inline', artistDisplay = 'inline' }) { 9 | 10 | return ( 11 | 12 | {songList.map((song, index) => ( 13 | 14 | ))} 15 | 16 | ) 17 | } 18 | 19 | export default ListOfSongs; 20 | -------------------------------------------------------------------------------- /client/src/components/Songs/OneSong.css: -------------------------------------------------------------------------------- 1 | .songPage { 2 | display: flex; 3 | flex-direction: row; 4 | } 5 | 6 | .descriptionArea { 7 | display: flex; 8 | flex-direction: column; 9 | } 10 | 11 | .iframe{ 12 | margin-top: 50px; 13 | margin-left: 50px; 14 | } 15 | 16 | .oneSongTitle { 17 | text-align: left; 18 | margin-left: 80px; 19 | font-size: 25px; 20 | } 21 | 22 | .oneSongLength { 23 | font-size: 15px; 24 | } 25 | 26 | .buttonsArea { 27 | display: flex; 28 | flex-direction: row; 29 | justify-content: space-between; 30 | width: 550px; 31 | margin: auto; 32 | margin-left: 100px; 33 | margin-bottom: 10px; 34 | margin-top: 15px; 35 | } 36 | 37 | .oneSongAlbum { 38 | text-align: left; 39 | margin-left: 80px; 40 | } 41 | 42 | .readMore { 43 | margin: auto; 44 | max-width: 580px; 45 | } 46 | 47 | .Suggestions { 48 | margin-top: 70px; 49 | margin-left: 80px; 50 | } 51 | 52 | 53 | .song-list { 54 | font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif; 55 | display: flex; 56 | flex-direction: column; 57 | list-style: none; 58 | /* margin: 10px auto 0px auto ; */ 59 | background-color:rgba(211, 211, 211, 0.534); 60 | color: black; 61 | border: 1px solid rgb(66, 55, 55); 62 | border-radius: 10px; 63 | max-width: 580px; 64 | width: 400px; 65 | height: 400px; 66 | overflow: auto; 67 | padding: 0px; 68 | margin-left: 100px; 69 | } 70 | 71 | .songRightSide { 72 | display: flex; 73 | flex-direction: column; 74 | } 75 | 76 | .song { 77 | display: flex; 78 | flex-direction: row; 79 | justify-content: space-between; 80 | margin: 10px 0px 0px 0px ; 81 | border-bottom: 1px solid rgb(82, 73, 73) ; 82 | } 83 | 84 | .song:hover { 85 | background-color: #999ca0; 86 | border: 1px solid #ffffff62; 87 | } 88 | 89 | .songName { 90 | margin-left: 10px; 91 | display: flex; 92 | flex-direction: row; 93 | color: black !important; 94 | width: 300px; 95 | text-align: left; 96 | } 97 | 98 | .nameAlbumArtist { 99 | margin-left: 10px; 100 | } 101 | 102 | .albumName { 103 | color: black !important; 104 | width: 200px; 105 | text-align: left; 106 | } 107 | 108 | .artistName { 109 | color: black !important; 110 | width: 200px; 111 | text-align: left; 112 | } 113 | 114 | .songLength { 115 | width: 100px; 116 | text-align: left; 117 | } 118 | 119 | .songDate { 120 | width: 150px; 121 | text-align: left; 122 | } 123 | 124 | .shareButton { 125 | cursor: pointer ; 126 | height: 25px; 127 | width: 25px; 128 | } 129 | 130 | .likeButton { 131 | cursor: pointer ; 132 | height: 25px; 133 | width: 25px; 134 | } 135 | 136 | .dislikeButton { 137 | cursor: pointer ; 138 | height: 25px; 139 | width: 25px; 140 | } 141 | 142 | .addToPlayListButton { 143 | cursor: pointer ; 144 | height: 25px; 145 | width: 25px; 146 | } 147 | 148 | .firstSong { 149 | margin-bottom: 0px; 150 | width: 100%; 151 | min-height: 40vh; 152 | } 153 | 154 | .moreLess { 155 | background-color: transparent; 156 | color: rgb(0, 179, 9); 157 | border: none; 158 | } 159 | 160 | #dropdownDropUp { 161 | background-color: transparent; 162 | border: none; 163 | background-image: url('../../images/addToPlayList.png'); 164 | background-size: 25px 25px; 165 | background-repeat: no-repeat; 166 | } -------------------------------------------------------------------------------- /client/src/components/Songs/Song.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import 'bootstrap/dist/css/bootstrap.min.css'; 4 | import './ListOfSongs.css'; 5 | import { GetYTId } from '../Services/globalVariables'; 6 | 7 | function Song({ query, song, albumDisplay, artistDisplay }) { 8 | return ( 9 |
  • 10 | 11 |
    12 | {song.name}
    13 | {song.Album.name}
    14 | {song.Artist.name} 15 |
    {song.length}
    16 | {song.createdAt ? 17 |
    {new Date(song.createdAt).toDateString()}
    18 | :
    19 | } 20 |
  • 21 | ); 22 | }; 23 | 24 | export default Song; 25 | -------------------------------------------------------------------------------- /client/src/components/Songs/SongsListForOneSong.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import 'bootstrap/dist/css/bootstrap.min.css'; 3 | import { Link } from 'react-router-dom'; 4 | import './OneSong.css'; 5 | import { GetYTId } from '../Services/globalVariables'; 6 | 7 | function SongsListForOneSong({ query, songList, split = 1, albumDisplay = 'inline', artistDisplay = 'inline' }) { 8 | 9 | return ( 10 |
      11 | {songList.map((song) => ( 12 |
    1. 13 | 14 | {''} 15 | 16 | 17 |
      {song.name}
      18 | {song.Album.name}
      19 | {song.Artist.name} 20 |
      21 | 22 |
    2. 23 | )).splice(split, songList.length - split)} 24 |
    25 | ) 26 | } 27 | 28 | export default SongsListForOneSong; 29 | -------------------------------------------------------------------------------- /client/src/images/Hilarious.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/Hilarious.gif -------------------------------------------------------------------------------- /client/src/images/SongIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/SongIcon.png -------------------------------------------------------------------------------- /client/src/images/addToPlayList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/addToPlayList.png -------------------------------------------------------------------------------- /client/src/images/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/admin.png -------------------------------------------------------------------------------- /client/src/images/album.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/album.png -------------------------------------------------------------------------------- /client/src/images/artist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/artist.png -------------------------------------------------------------------------------- /client/src/images/disLike.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/disLike.png -------------------------------------------------------------------------------- /client/src/images/dislikeActive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/dislikeActive.png -------------------------------------------------------------------------------- /client/src/images/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/home.png -------------------------------------------------------------------------------- /client/src/images/like.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/like.png -------------------------------------------------------------------------------- /client/src/images/likeActive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/likeActive.png -------------------------------------------------------------------------------- /client/src/images/myLibrary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/myLibrary.png -------------------------------------------------------------------------------- /client/src/images/pageNotFound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/pageNotFound.png -------------------------------------------------------------------------------- /client/src/images/playbutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/playbutton.png -------------------------------------------------------------------------------- /client/src/images/playlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/playlist.png -------------------------------------------------------------------------------- /client/src/images/shareButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/shareButton.png -------------------------------------------------------------------------------- /client/src/images/shareButton1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/shareButton1.png -------------------------------------------------------------------------------- /client/src/images/userName.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/client/src/images/userName.png -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import * as serviceWorker from './serviceWorker'; 5 | import './App.css'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.unregister(); 18 | -------------------------------------------------------------------------------- /client/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /client/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /readme-files/logo-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/readme-files/logo-main.png -------------------------------------------------------------------------------- /readme-files/my-ERD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/readme-files/my-ERD.png -------------------------------------------------------------------------------- /readme-files/my-app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/Spotify-MySQL-ORM/85e7c414b821e3efd7d27b3976fe959a318a3234/readme-files/my-app.gif -------------------------------------------------------------------------------- /server/Routes/users.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const express = require('express'); 3 | const usersRouter = express.Router(); 4 | const { User } = require('../models'); 5 | const jwt = require('jsonwebtoken'); 6 | const bcrypt = require('bcrypt'); 7 | 8 | function pad(num) { return ('00' + num).slice(-2) }; 9 | 10 | // Change the date to SQL date format 11 | function formatDate(date) { 12 | let dateStr = date.getUTCFullYear() + '-' + 13 | pad(date.getUTCMonth() + 1) + '-' + 14 | pad(date.getUTCDate() + 1) 15 | return dateStr; 16 | }; 17 | 18 | // Register 19 | usersRouter.post('/register', async (req, res) => { 20 | const { name, email, password } = req.body; 21 | try { 22 | const result = await User.findOne({ 23 | where: { email: email } 24 | }) 25 | if (!result) { 26 | const hashedPassword = await bcrypt.hash(password, 10); 27 | const newUser = { 28 | name, 29 | email, 30 | password: hashedPassword, 31 | createdAt: formatDate(new Date()), 32 | updatedAt: formatDate(new Date()) 33 | } 34 | const userResponse = await User.create(newUser); 35 | res.json({ 36 | message: "1 user successfully inserted into db", 37 | name: userResponse.name, 38 | email: userResponse.email 39 | }); 40 | 41 | } else { 42 | res.status(406).json({ message: 'Email alreay taken!' }) 43 | } 44 | } catch (error) { 45 | console.error(error); 46 | res.status(400).json({ message: error.message }); 47 | }; 48 | }) 49 | 50 | // Validate Token 51 | usersRouter.post("/valid", (req, res) => { 52 | jwt.verify(req.body.token, process.env.SECRET_KEY, (error, data) => { 53 | if (error) { 54 | res.status(403).json({message: error}); 55 | } else { 56 | res.json({ valid: true, isAdmin: data.isAdmin }) 57 | } 58 | }) 59 | }) 60 | 61 | // Log In 62 | usersRouter.post("/logIn", async (req, res) => { 63 | const { email, password, rememberToken } = req.body; 64 | try { 65 | const user = await User.findOne({ where: { email: email } }); 66 | if (await bcrypt.compare(password, user.password)) { 67 | const newToken = { 68 | isAdmin: user.isAdmin, 69 | email: user.email, 70 | userId: user.id, 71 | } 72 | if (!rememberToken) { 73 | newToken.rememberToken = rememberToken, 74 | newToken.exp = Math.floor(Date.now() / 1000) + (60 * 30) 75 | } 76 | const token = jwt.sign(newToken, process.env.SECRET_KEY) 77 | res.cookie('name', user.name) 78 | res.cookie('isAdmin', user.isAdmin) 79 | res.cookie('token', token) 80 | res.cookie('user', user.email) 81 | res.header('Authorization', token) 82 | res.json(`welcome back ${user.name}`) 83 | } else { 84 | res.status(403).json({ message: 'The email or password you’ve entered doesn’t correct.' }); 85 | } 86 | } catch (error) { 87 | console.error(error); 88 | res.status(400).json({ message: error.message }); 89 | }; 90 | }) 91 | 92 | module.exports = usersRouter; 93 | -------------------------------------------------------------------------------- /server/api/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const api = express.Router(); 3 | 4 | api.use('/v1',require('./v1')); 5 | 6 | module.exports = api; -------------------------------------------------------------------------------- /server/api/v1/albums.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const albumsRouter = express.Router(); 3 | const checkAdmin = require('../../helpers/checkAdmin'); 4 | const { Artist, Album, Song } = require('../../models'); 5 | const { Op } = require("sequelize"); 6 | 7 | // get all albums 8 | albumsRouter.get("/", async (req, res) => { 9 | try { 10 | const allAlbums = await Album.findAll({ 11 | include: [{ 12 | model: Artist, 13 | attributes: ["name"] 14 | }, { 15 | model: Song, 16 | include: [ 17 | { 18 | model: Artist, 19 | attributes: ["name"], 20 | }, 21 | { 22 | model: Album, 23 | attributes: ["name"], 24 | }, 25 | ], 26 | } 27 | ] 28 | }); 29 | res.json(allAlbums); 30 | } catch (e) { 31 | res.status(400).json({ message: error.message });; 32 | }; 33 | }); 34 | 35 | // get album filtered by id from params 36 | albumsRouter.get("/byId/:id", async (req, res) => { 37 | try { 38 | const result = await Album.findByPk(req.params.id, { 39 | include: [Artist, { 40 | model: Song, 41 | include: [ 42 | { 43 | model: Artist, 44 | attributes: ["name"], 45 | }, 46 | { 47 | model: Album, 48 | attributes: ["name"], 49 | }, 50 | ], 51 | }] 52 | }); 53 | res.json(result); 54 | } catch (error) { 55 | console.error(error); 56 | res.status(400).json({ message: error.message }); 57 | }; 58 | }); 59 | 60 | // get album filtered by name from params 61 | albumsRouter.get("/byName/:name", async (req, res) => { 62 | try { 63 | const results = await Album.findAll({ 64 | where: { 65 | name: { 66 | [Op.like]: `%${req.params.name}%` 67 | } 68 | } 69 | }); 70 | res.json(results); 71 | } catch (error) { 72 | console.error(error); 73 | res.status(400).json({ message: error.message }); 74 | }; 75 | }); 76 | 77 | // get top 20 albums 78 | albumsRouter.get("/top", async (req, res) => { 79 | try { 80 | const allAlbums = await Album.findAll({ limit: 20 }); 81 | res.json(allAlbums); 82 | } catch (error) { 83 | console.error(error); 84 | res.status(400).json({ message: error.message }); 85 | }; 86 | }); 87 | 88 | //============================== Admin Routes ======================================// 89 | // create new album 90 | albumsRouter.post("/", checkAdmin, async (req, res) => { 91 | try { 92 | const { body } = req; 93 | const newAlbum = await Album.create(body); 94 | res.json(newAlbum); 95 | } catch (error) { 96 | console.error(error); 97 | res.status(400).json({ message: error.message }); 98 | }; 99 | }); 100 | 101 | // update album information 102 | albumsRouter.put("/:id", checkAdmin, async (req, res) => { 103 | try { 104 | const { body } = req; 105 | const editAlbum = await Album.update(body, { 106 | where: { id: req.params.id } 107 | }) 108 | res.json(editAlbum); 109 | } catch (error) { 110 | console.error(error); 111 | res.status(400).json({ message: error.message }); 112 | }; 113 | }); 114 | 115 | // delete album 116 | albumsRouter.delete("/:id", checkAdmin, async (req, res) => { 117 | try { 118 | const result = await Album.destroy({ 119 | where: { id: req.params.id } 120 | }) 121 | res.json(result); 122 | } catch (error) { 123 | console.error(error); 124 | res.status(400).json({ message: error.message }); 125 | }; 126 | }); 127 | 128 | module.exports = albumsRouter; 129 | -------------------------------------------------------------------------------- /server/api/v1/artists.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const artistsRouter = express.Router(); 3 | const checkAdmin = require('../../helpers/checkAdmin'); 4 | const { Artist, Album, Song } = require('../../models'); 5 | const { Op } = require("sequelize"); 6 | 7 | // get all artists 8 | artistsRouter.get("/", async (req, res) => { 9 | try { 10 | const allArtists = await Artist.findAll({ 11 | include: [{ 12 | model: Album, 13 | attributes: ["name"] 14 | }, { 15 | model: Song, 16 | include: [ 17 | { 18 | model: Artist, 19 | attributes: ["name"], 20 | }, 21 | { 22 | model: Album, 23 | attributes: ["name"], 24 | }, 25 | ], 26 | } 27 | ] 28 | }); 29 | res.json(allArtists); 30 | } catch (error) { 31 | console.error(error); 32 | res.status(400).json({ message: error.message }); 33 | }; 34 | }); 35 | 36 | // get artist filtered by id from params 37 | artistsRouter.get('/byId/:id', async (req, res) => { 38 | try { 39 | const result = await Artist.findByPk(req.params.id, { 40 | include: [Album, { 41 | model: Song, 42 | include: [ 43 | { 44 | model: Artist, 45 | attributes: ["name"], 46 | }, 47 | { 48 | model: Album, 49 | attributes: ["name"], 50 | }, 51 | ], 52 | }] 53 | }); 54 | res.json(result); 55 | } catch (error) { 56 | console.error(error); 57 | res.status(400).json({ message: error.message }); 58 | }; 59 | }); 60 | 61 | // get artist filtered by name from params 62 | artistsRouter.get("/byName/:name", async (req, res) => { 63 | try { 64 | const result = await Artist.findAll({ 65 | where: { 66 | name: { 67 | [Op.like]: `%${req.params.name}%` 68 | } 69 | } 70 | }); 71 | res.json(result); 72 | } catch (error) { 73 | console.error(error); 74 | res.status(400).json({ message: error.message }); 75 | }; 76 | }); 77 | 78 | // get top 20 artists 79 | artistsRouter.get("/top", async (req, res) => { 80 | try { 81 | const allArtists = await Artist.findAll({ limit: 20 }); 82 | res.json(allArtists); 83 | } catch (error) { 84 | console.error(error); 85 | res.status(400).json({ message: error.message }); 86 | }; 87 | }); 88 | 89 | //============================== Admin Routes ======================================// 90 | 91 | // create new artist 92 | artistsRouter.post("/", checkAdmin, async (req, res) => { 93 | try { 94 | const { body } = req; 95 | const newAtrist = await Artist.create(body); 96 | res.json(newAtrist); 97 | } catch (error) { 98 | console.error(error); 99 | res.status(400).json({ message: error.message }); 100 | }; 101 | }); 102 | 103 | // update artist information 104 | artistsRouter.put("/:id", checkAdmin, async (req, res) => { 105 | try { 106 | const { body } = req; 107 | const editArtist = await Artist.update(body, { 108 | where: { id: req.params.id } 109 | }) 110 | res.json(editArtist); 111 | } catch (error) { 112 | console.error(error); 113 | res.status(400).json({ message: error.message }); 114 | }; 115 | }); 116 | 117 | // delete artist 118 | artistsRouter.delete("/:id", checkAdmin, async (req, res) => { 119 | try { 120 | const result = await Artist.destroy({ 121 | where: { id: req.params.id } 122 | }) 123 | res.json(result); 124 | } catch (error) { 125 | console.error(error); 126 | res.status(400).json({ message: error.message }); 127 | }; 128 | }); 129 | 130 | module.exports = artistsRouter; 131 | -------------------------------------------------------------------------------- /server/api/v1/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const index = express.Router(); 3 | const checkAdmin = require('../../helpers/checkAdmin'); 4 | 5 | index.use('/songs', require('./songs')); 6 | 7 | index.use('/albums', require('./albums')); 8 | 9 | index.use('/artists', require('./artists')); 10 | 11 | index.use('/playlists', require('./playlists')); 12 | 13 | index.use('/songsInPlaylists', require('./songsInPlaylists')) 14 | 15 | index.use('/interactions', require('./interactions')) 16 | 17 | //============================== Admin Routes ======================================// 18 | 19 | index.use('/users', checkAdmin, require('./users')) 20 | 21 | 22 | module.exports = index; 23 | -------------------------------------------------------------------------------- /server/api/v1/interactionRoues/albumsInteractions.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const AlbumsInteractions = express.Router(); 3 | const { User_album, Song, Artist, Album } = require('../../../models'); 4 | const { Op } = require("sequelize"); 5 | 6 | // get all albums interactions 7 | AlbumsInteractions.get("/userInteractions", async (req, res) => { 8 | try { 9 | const allInteractions = await User_album.findAll({ 10 | where: { 11 | email: req.decoded.email 12 | }, 13 | order: [ 14 | ['id', 'ASC'] 15 | ] 16 | }) 17 | res.json(allInteractions) 18 | } catch (error) { 19 | console.error(error); 20 | res.status(400).json({ message: error.message }); 21 | }; 22 | }); 23 | 24 | // get albums interactions filtered by user email 25 | AlbumsInteractions.post("/", async (req, res) => { 26 | try { 27 | const { body } = req; 28 | body.email = req.decoded.email; 29 | const existInteraction = await User_album.findOne({ 30 | where: { 31 | email: req.decoded.email, 32 | albumId: body.albumId 33 | } 34 | }) 35 | if (existInteraction) { 36 | const editInteraction = await User_album.update(body, { 37 | where: { 38 | email: req.decoded.email, 39 | albumId: body.albumId 40 | } 41 | }); 42 | res.json(editInteraction); 43 | } else { 44 | const newInteraction = await User_album.create(body); 45 | res.json(newInteraction); 46 | } 47 | } catch (error) { 48 | console.error(error); 49 | res.status(400).json({ message: error.message }); 50 | }; 51 | }); 52 | 53 | // create new interaction with a album 54 | AlbumsInteractions.post("/byUser", async (req, res) => { 55 | try { 56 | const allInteractions = await User_album.findAll({ 57 | where: { 58 | email: req.decoded.email, 59 | albumId: { [Op.in]: req.body } 60 | }, 61 | include: [{ 62 | model: Album, 63 | include: [{ 64 | model: Song, 65 | include: [ 66 | { 67 | model: Artist, 68 | attributes: ["name"], 69 | }, 70 | { 71 | model: Album, 72 | attributes: ["name"], 73 | }, 74 | ], 75 | }], 76 | } 77 | ] 78 | }) 79 | res.json(allInteractions) 80 | } catch (error) { 81 | console.error(error); 82 | res.status(400).json({ message: error.message }); 83 | }; 84 | }); 85 | 86 | module.exports = AlbumsInteractions; 87 | -------------------------------------------------------------------------------- /server/api/v1/interactionRoues/artistsInteractions.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const ArtistsInteractions = express.Router(); 3 | const { User_artist, Song, Artist, Album } = require('../../../models'); 4 | const { Op } = require("sequelize"); 5 | 6 | // get all artists interactions 7 | ArtistsInteractions.get("/userInteractions", async (req, res) => { 8 | try { 9 | const allInteractions = await User_artist.findAll({ 10 | where: { 11 | email: req.decoded.email 12 | }, 13 | order: [ 14 | ['id', 'ASC'] 15 | ] 16 | }) 17 | res.json(allInteractions) 18 | } catch (error) { 19 | console.error(error); 20 | res.status(400).json({ message: error.message }); 21 | }; 22 | }); 23 | 24 | // get artists interactions filtered by user email 25 | ArtistsInteractions.post("/", async (req, res) => { 26 | try { 27 | const { body } = req; 28 | body.email = req.decoded.email; 29 | const existInteraction = await User_artist.findOne({ 30 | where: { 31 | email: req.decoded.email, 32 | artistId: body.artistId 33 | } 34 | }) 35 | if (existInteraction) { 36 | const editInteraction = await User_artist.update(body, { 37 | where: { 38 | email: req.decoded.email, 39 | artistId: body.artistId 40 | } 41 | }); 42 | res.json(editInteraction); 43 | } else { 44 | const newInteraction = await User_artist.create(body); 45 | res.json(newInteraction); 46 | } 47 | } catch (error) { 48 | console.error(error); 49 | res.status(400).json({ message: error.message }); 50 | }; 51 | }); 52 | 53 | // create new interaction with a artist 54 | ArtistsInteractions.post("/byUser", async (req, res) => { 55 | try { 56 | const allInteractions = await User_artist.findAll({ 57 | where: 58 | { 59 | [Op.or]: [ 60 | { email: req.decoded.email } 61 | ], 62 | id: { [Op.in]: req.body } 63 | }, 64 | include: [{ 65 | model: Artist, 66 | include: [{ 67 | model: Song, 68 | include: [ 69 | { 70 | model: Artist, 71 | attributes: ["name"], 72 | }, 73 | { 74 | model: Album, 75 | attributes: ["name"], 76 | }, 77 | ], 78 | }], 79 | } 80 | ] 81 | }) 82 | res.json(allInteractions) 83 | } catch (error) { 84 | console.error(error); 85 | res.status(400).json({ message: error.message }); 86 | }; 87 | }); 88 | 89 | module.exports = ArtistsInteractions; 90 | -------------------------------------------------------------------------------- /server/api/v1/interactionRoues/playlistsInteractions.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const PlaylistsInteractions = express.Router(); 3 | const { User_playlist, Song, Playlist, Artist, Album, PlaylistsSong } = require('../../../models'); 4 | const { Op } = require("sequelize"); 5 | 6 | // get all playlists interactions 7 | PlaylistsInteractions.get("/all", async (req, res) => { 8 | try { 9 | const allInteractions = await User_playlist.findAll({ 10 | where: 11 | { 12 | [Op.or]: [ 13 | { email: req.decoded.email }, 14 | { email: 'david@gmail.com' } 15 | ] 16 | }, 17 | include: [{ 18 | model: Playlist, 19 | include: [ 20 | { 21 | model: PlaylistsSong, 22 | attributes: ["id"], 23 | include: [{ 24 | model: Song, 25 | include: [ 26 | { 27 | model: Artist, 28 | attributes: ["name"], 29 | }, 30 | { 31 | model: Album, 32 | attributes: ["name"], 33 | }, 34 | ], 35 | }], 36 | } 37 | ] 38 | }] 39 | }) 40 | res.json(allInteractions) 41 | } catch (error) { 42 | console.error(error); 43 | res.status(400).json({ message: error.message }); 44 | }; 45 | }); 46 | 47 | // get playlists interactions filtered by user email 48 | PlaylistsInteractions.get("/byUser", async (req, res) => { 49 | try { 50 | const allInteractions = await User_playlist.findAll({ 51 | where: 52 | { email: req.decoded.email }, 53 | include: [{ 54 | model: Playlist, 55 | include: [ 56 | { 57 | model: PlaylistsSong, 58 | attributes: ["id"], 59 | include: [{ 60 | model: Song, 61 | include: [ 62 | { 63 | model: Artist, 64 | attributes: ["name"], 65 | }, 66 | { 67 | model: Album, 68 | attributes: ["name"], 69 | }, 70 | ], 71 | }], 72 | } 73 | ] 74 | }] 75 | }) 76 | res.json(allInteractions) 77 | } catch (error) { 78 | console.error(error); 79 | res.status(400).json({ message: error.message }); 80 | }; 81 | }); 82 | 83 | // create new interaction with a playlist 84 | PlaylistsInteractions.post("/", async (req, res) => { 85 | try { 86 | const { body } = req; 87 | body.email = req.decoded.email; 88 | const newInteraction = await User_playlist.create(body); 89 | res.json(newInteraction); 90 | } catch (error) { 91 | console.error(error); 92 | res.status(400).json({ message: error.message }); 93 | }; 94 | }); 95 | 96 | module.exports = PlaylistsInteractions; 97 | -------------------------------------------------------------------------------- /server/api/v1/interactionRoues/songsInteractions.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const SongsInteractions = express.Router(); 3 | const { Interaction,Song,Artist,Album } = require('../../../models'); 4 | const {Op} = require('sequelize') 5 | 6 | // get all songs interactions 7 | SongsInteractions.get("/", async (req, res) => { 8 | try { 9 | const allInteractions = await Interaction.findAll() 10 | res.json(allInteractions) 11 | } catch (error) { 12 | console.error(error); 13 | res.status(400).json({ message: error.message }); 14 | }; 15 | }); 16 | 17 | // get songs interactions filtered by user email 18 | SongsInteractions.get("/byUser", async (req, res) => { 19 | try { 20 | const allInteractions = await Interaction.findAll({ 21 | where: { 22 | userId: req.decoded.userId 23 | } 24 | }) 25 | res.json(allInteractions) 26 | } catch (error) { 27 | console.error(error); 28 | res.status(400).json({ message: error.message }); 29 | }; 30 | }); 31 | 32 | // create new interaction with a song 33 | SongsInteractions.post("/", async (req, res) => { 34 | try { 35 | const { body } = req; 36 | body.userId = req.decoded.userId 37 | const newInteraction = await Interaction.create(body); 38 | res.json(newInteraction); 39 | } catch (error) { 40 | console.error(error); 41 | res.status(400).json({ message: error.message }); 42 | }; 43 | }); 44 | 45 | // get favorite songs filtered by user email 46 | SongsInteractions.post("/songsByUser", async (req, res) => { 47 | try { 48 | const result = await Song.findAll({ 49 | where: { 50 | id: { [Op.in]: req.body } 51 | }, 52 | include: [ 53 | { 54 | model: Artist, 55 | attributes: ["name"], 56 | }, 57 | { 58 | model: Album, 59 | attributes: ["name"], 60 | }, 61 | ] 62 | }); 63 | res.json(result); 64 | } catch (error) { 65 | console.error(error); 66 | res.status(400).json({ message: error.message });; 67 | }; 68 | }); 69 | 70 | 71 | module.exports = SongsInteractions; 72 | -------------------------------------------------------------------------------- /server/api/v1/interactions.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const Interactions = express.Router(); 3 | 4 | Interactions.use('/songs', require('./interactionRoues/songsInteractions')); 5 | 6 | Interactions.use('/albums', require('./interactionRoues/albumsInteractions')); 7 | 8 | Interactions.use('/artists', require('./interactionRoues/artistsInteractions')); 9 | 10 | Interactions.use('/playlists', require('./interactionRoues/playlistsInteractions')); 11 | 12 | module.exports = Interactions; 13 | -------------------------------------------------------------------------------- /server/api/v1/playlists.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const playlistsRouter = express.Router(); 3 | const checkAdmin = require('../../helpers/checkAdmin'); 4 | const { Song, Playlist, Artist, Album, PlaylistsSong } = require('../../models'); 5 | const { Op } = require("sequelize"); 6 | 7 | // get all playlists 8 | playlistsRouter.get("/", async (req, res) => { 9 | try { 10 | const allPlaylists = await Playlist.findAll({ 11 | include: [ 12 | { 13 | model: PlaylistsSong, 14 | attributes: ["id"], 15 | include: [{ 16 | model: Song, 17 | include: [ 18 | { 19 | model: Artist, 20 | attributes: ["name"], 21 | }, 22 | { 23 | model: Album, 24 | attributes: ["name"], 25 | }, 26 | ], 27 | }], 28 | } 29 | ] 30 | }); 31 | res.json(allPlaylists); 32 | } catch (error) { 33 | console.error(error); 34 | res.status(400).json({ message: error.message }); 35 | }; 36 | }); 37 | 38 | // get playlist filtered by id from params 39 | playlistsRouter.get("/byId/:id", async (req, res) => { 40 | try { 41 | const result = await Playlist.findByPk(req.params.id, { 42 | include: [ 43 | { 44 | model: PlaylistsSong, 45 | attributes: ["id"], 46 | include: [{ 47 | model: Song, 48 | include: [ 49 | { 50 | model: Artist, 51 | attributes: ["name"], 52 | }, 53 | { 54 | model: Album, 55 | attributes: ["name"], 56 | }, 57 | ], 58 | }], 59 | } 60 | ] 61 | }); 62 | res.json(result); 63 | } catch (error) { 64 | console.error(error); 65 | res.status(400).json({ message: error.message }); 66 | }; 67 | }); 68 | 69 | // get playlist filtered by name from params 70 | playlistsRouter.get("/byName/:name", async (req, res) => { 71 | try { 72 | const results = await Playlist.findAll({ 73 | where: { 74 | name: { 75 | [Op.like]: `%${req.params.name}%` 76 | } 77 | } 78 | }); 79 | res.json(results); 80 | } catch (error) { 81 | console.error(error); 82 | res.status(400).json({ message: error.message }); 83 | }; 84 | }); 85 | 86 | // get top 20 playlists 87 | playlistsRouter.get("/top", async (req, res) => { 88 | try { 89 | const allPlaylists = await Playlist.findAll({ limit: 20 }); 90 | res.json(allPlaylists); 91 | } catch (error) { 92 | console.error(error); 93 | res.status(400).json({ message: error.message }); 94 | }; 95 | }); 96 | 97 | // create new playlist 98 | playlistsRouter.post("/", async (req, res) => { 99 | try { 100 | const { body } = req; 101 | body.userId = req.decoded.userId; 102 | const newPlaylist = await Playlist.create(body); 103 | res.json(newPlaylist); 104 | } catch (error) { 105 | console.error(error); 106 | res.status(400).json({ message: error.message }); 107 | }; 108 | }); 109 | 110 | //============================== Admin Routes ======================================// 111 | 112 | // update playlist information 113 | playlistsRouter.put("/:id", checkAdmin, async (req, res) => { 114 | try { 115 | const { body } = req; 116 | const editPlaylist = await Playlist.update(body, { 117 | where: { id: req.params.id } 118 | }) 119 | res.json(editPlaylist); 120 | } catch (error) { 121 | console.error(error); 122 | res.status(400).json({ message: error.message }); 123 | }; 124 | }); 125 | 126 | // delete playlist 127 | playlistsRouter.delete("/:id", checkAdmin, async (req, res) => { 128 | try { 129 | const result = await Playlist.destroy({ 130 | where: { id: req.params.id } 131 | }) 132 | res.json(result); 133 | } catch (error) { 134 | console.error(error); 135 | res.status(400).json({ message: error.message }); 136 | }; 137 | }); 138 | 139 | module.exports = playlistsRouter; 140 | -------------------------------------------------------------------------------- /server/api/v1/songs.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const songsRouter = express.Router(); 3 | const checkAdmin = require('../../helpers/checkAdmin'); 4 | const { Artist, Album, Song } = require('../../models'); 5 | const { Op } = require("sequelize"); 6 | 7 | // get all songs 8 | songsRouter.get("/", async (req, res) => { 9 | try { 10 | const allSongs = await Song.findAll(); 11 | res.json(allSongs); 12 | } catch (error) { 13 | console.error(error); 14 | res.status(400).json({ message: error.message }); 15 | }; 16 | }); 17 | 18 | // get song filtered by id from params 19 | songsRouter.get("/byId/:id", async (req, res) => { 20 | try { 21 | const result = await Song.findByPk(req.params.id, { 22 | include: [ 23 | { 24 | model: Artist, 25 | attributes: ["name"], 26 | }, 27 | { 28 | model: Album, 29 | attributes: ["name"], 30 | }, 31 | ] 32 | }); 33 | res.json(result); 34 | } catch (error) { 35 | console.error(error); 36 | res.status(400).json({ message: error.message });; 37 | }; 38 | }); 39 | 40 | // get song filtered by name from params 41 | songsRouter.get("/byName/:name", async (req, res) => { 42 | try { 43 | const result = await Song.findAll({ 44 | where: { 45 | name: { 46 | [Op.like]: `%${req.params.name}%` 47 | } 48 | } 49 | }); 50 | res.json(result); 51 | } catch (error) { 52 | console.error(error); 53 | res.status(400).json({ message: error.message });; 54 | }; 55 | }); 56 | 57 | // get top 20 songs 58 | songsRouter.get("/top", async (req, res) => { 59 | try { 60 | const allSongs = await Song.findAll({ 61 | limit: 20, include: [ 62 | { 63 | model: Artist, 64 | attributes: ["name"], 65 | }, 66 | { 67 | model: Album, 68 | attributes: ["name"], 69 | }, 70 | ] 71 | }); 72 | res.json(allSongs); 73 | } catch (error) { 74 | console.error(error); 75 | res.status(400).json({ message: error.message });; 76 | }; 77 | }); 78 | 79 | //============================== Admin Routes ======================================// 80 | 81 | // create new song 82 | songsRouter.post("/", checkAdmin, async (req, res) => { 83 | try { 84 | const { body } = req; 85 | const newSong = await Song.create(body); 86 | res.json(newSong); 87 | } catch (error) { 88 | console.error(error); 89 | res.status(400).json({ message: error.message });; 90 | }; 91 | }); 92 | 93 | // update song information 94 | songsRouter.put("/:id", checkAdmin, async (req, res) => { 95 | try { 96 | const { body } = req; 97 | const editSong = await Song.update(body, { 98 | where: { id: req.params.id } 99 | }) 100 | res.json(editSong); 101 | } catch (error) { 102 | console.error(error); 103 | res.status(400).json({ message: error.message });; 104 | }; 105 | }); 106 | 107 | // delete song 108 | songsRouter.delete("/:id", checkAdmin, async (req, res) => { 109 | try { 110 | const result = await Song.destroy({ 111 | where: { id: req.params.id } 112 | }) 113 | res.json(result); 114 | } catch (error) { 115 | console.error(error); 116 | res.status(400).json({ message: error.message });; 117 | }; 118 | }); 119 | 120 | module.exports = songsRouter; 121 | -------------------------------------------------------------------------------- /server/api/v1/songsInPlaylists.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const SongsInPlaylistsRouter = express.Router(); 3 | const { PlaylistsSong } = require('../../models'); 4 | 5 | // add song to a playlist 6 | SongsInPlaylistsRouter.post("/", async (req, res) => { 7 | try { 8 | const { body } = req; 9 | const newSongInPlaylist = await PlaylistsSong.create(body); 10 | res.json(newSongInPlaylist); 11 | } catch (error) { 12 | console.error(error); 13 | res.status(400).json({ message: error.message }); 14 | }; 15 | }); 16 | 17 | // remove song from a playlist 18 | SongsInPlaylistsRouter.delete("/", async (req, res) => { 19 | const { songId, playlistId } = req.query; 20 | try { 21 | const result = await PlaylistsSong.destroy({ 22 | where: { songId, playlistId } 23 | }) 24 | res.json(result); 25 | } catch (error) { 26 | console.error(error); 27 | res.status(400).json({ message: error.message }); 28 | }; 29 | }); 30 | 31 | module.exports = SongsInPlaylistsRouter; 32 | -------------------------------------------------------------------------------- /server/api/v1/users.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const usersRouter = express.Router(); 3 | const { User } = require('../../models'); 4 | const { Op } = require("sequelize"); 5 | 6 | // get all users 7 | usersRouter.get("/", async (req, res) => { 8 | try { 9 | const allUsers = await User.findAll({}); 10 | res.json(allUsers); 11 | } catch (error) { 12 | console.error(error); 13 | res.status(400).json({ message: error.message });; 14 | }; 15 | }); 16 | 17 | // get user filtered by id from params 18 | usersRouter.get("/byId/:id", async (req, res) => { 19 | try { 20 | const user = await User.findByPk(req.params.id); 21 | res.json(user); 22 | } catch (error) { 23 | console.error(error); 24 | res.status(400).json({ message: error.message }); 25 | }; 26 | }); 27 | 28 | // get user filtered by name from params 29 | usersRouter.get("/byName/:name", async (req, res) => { 30 | try { 31 | const users = await User.findAll({ 32 | where: { 33 | name: { 34 | [Op.like]: `%${req.params.name}%` 35 | } 36 | } 37 | }); 38 | res.json(users); 39 | } catch (error) { 40 | console.error(error); 41 | res.status(400).json({ message: error.message }); 42 | }; 43 | }); 44 | 45 | // create new user 46 | usersRouter.post("/", async (req, res) => { 47 | try { 48 | const { body } = req; 49 | const newUser = await User.create(body); 50 | res.json(newUser); 51 | } catch (error) { 52 | console.error(error); 53 | res.status(400).json({ message: error.message }); 54 | }; 55 | }); 56 | 57 | // update user information 58 | usersRouter.put("/:id", async (req, res) => { 59 | try { 60 | const { body } = req; 61 | const editUser = await User.update(body, { 62 | where: { id: req.params.id } 63 | }) 64 | res.json(editUser); 65 | } catch (error) { 66 | console.error(error); 67 | res.status(400).json({ message: error.message }); 68 | }; 69 | }); 70 | 71 | // delete user 72 | usersRouter.delete("/:id", async (req, res) => { 73 | try { 74 | const result = await User.destroy({ 75 | where: { id: req.params.id } 76 | }) 77 | res.json(result); 78 | } catch (error) { 79 | console.error(error); 80 | res.status(400).json({ message: error.message }); 81 | }; 82 | }); 83 | 84 | module.exports = usersRouter; 85 | -------------------------------------------------------------------------------- /server/config/config.js: -------------------------------------------------------------------------------- 1 | 2 | require('dotenv').config(); 3 | module.exports = { 4 | "development": { 5 | "username": process.env.USER, 6 | "password": process.env.PASSWORD, 7 | "database": process.env.DEV_DB, 8 | "host": "127.0.0.1", 9 | "logging": false, 10 | "dialect": "mysql", 11 | "define": {"underscored": true} 12 | }, 13 | "test": { 14 | "username": process.env.USER, 15 | "password": process.env.PASSWORD, 16 | "database": process.env.TEST_DB, 17 | "host": "127.0.0.1", 18 | "dialect": "mysql", 19 | "define": {"underscored": true} 20 | }, 21 | "production": { 22 | "username": "root", 23 | "password": null, 24 | "database": "database_production", 25 | "host": "127.0.0.1", 26 | "dialect": "mysql", 27 | "define": {"underscored": true} 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /server/connection.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const mysql = require('mysql'); 3 | 4 | const DataBase = mysql.createConnection({ 5 | host: "localhost", 6 | user: process.env.USER, 7 | password: process.env.PASSWORD, 8 | database: process.env.DATA_BASE 9 | }); 10 | 11 | DataBase.connect((err) => { 12 | if (err) { console.error(err) } 13 | else { 14 | console.log(`Connected to my sql! on ${process.env.DATA_BASE} DataBase`); 15 | } 16 | }); 17 | 18 | module.exports = DataBase; 19 | -------------------------------------------------------------------------------- /server/helpers/checkAdmin.js: -------------------------------------------------------------------------------- 1 | const { User } = require("../models"); 2 | 3 | module.exports = function checkAdmin(req, res, next) { 4 | User.findOne({ 5 | where: { email: req.decoded.email }, 6 | }).then((user) => { 7 | if (user.isAdmin) { 8 | next(); 9 | } else { 10 | res.sendStatus(401); 11 | } 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /server/helpers/errorHandler.js: -------------------------------------------------------------------------------- 1 | module.exports = function (error, request, response, next) { 2 | console.error(error.message); 3 | 4 | if (error.name === 'CastError') { 5 | return response.status(400).json({ error: 'malformatted id' }); 6 | }; 7 | 8 | next(error); 9 | }; 10 | -------------------------------------------------------------------------------- /server/helpers/morgan.js: -------------------------------------------------------------------------------- 1 | const morgan = require('morgan'); 2 | 3 | module.exports = morgan(function (tokens, req, res) { 4 | const myTiny = [tokens.method(req, res), 5 | tokens.url(req, res), 6 | tokens.status(req, res), 7 | tokens.res(req, res, 'content-length'), '-', 8 | tokens['response-time'](req, res), 'ms'] 9 | if (req.method === 'POST' || req.method === 'PUT') { 10 | return myTiny.concat([JSON.stringify(req.body)]).join(' ') 11 | } else { 12 | return myTiny.join(' ') 13 | } 14 | }) -------------------------------------------------------------------------------- /server/helpers/tokenCheck.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const jwt = require('jsonwebtoken'); 3 | 4 | module.exports = function (req, res, next) { 5 | const bearerHeader = req.headers['authorization']; 6 | if (typeof bearerHeader !== 'undefined') { 7 | jwt.verify(bearerHeader, process.env.SECRET_KEY, (error, decoded) => { 8 | if (error) { 9 | res.status(403).json({ message: error }); 10 | } else { 11 | const newToken = { 12 | isAdmin: decoded.isAdmin, 13 | email: decoded.email, 14 | userId: decoded.userId 15 | } 16 | if (!decoded.rememberToken) { 17 | newToken.exp = Math.floor(Date.now() / 1000) + (60 * 30) 18 | }; 19 | const token = jwt.sign(newToken, process.env.SECRET_KEY); 20 | req.decoded = decoded 21 | res.cookie('token', token); 22 | next(); 23 | } 24 | }) 25 | } else { 26 | res.status(403).json({ message: 'token is requierd' }); 27 | } 28 | } -------------------------------------------------------------------------------- /server/helpers/unknownEndpoint.js: -------------------------------------------------------------------------------- 1 | module.exports = function (request, response) { 2 | response.status(404).json({ error: 'unknown endpoint' }); 3 | }; 4 | 5 | -------------------------------------------------------------------------------- /server/migrations/20200923121830-create-song.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable('Songs', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | youtube_link: { 12 | type: Sequelize.STRING 13 | }, 14 | album_id: { 15 | type: Sequelize.INTEGER 16 | }, 17 | artist_id: { 18 | type: Sequelize.INTEGER 19 | }, 20 | name: { 21 | type: Sequelize.STRING 22 | }, 23 | length: { 24 | type: Sequelize.TIME 25 | }, 26 | track_number: { 27 | type: Sequelize.INTEGER 28 | }, 29 | lyrics: { 30 | type: Sequelize.TEXT 31 | }, 32 | upload_at: { 33 | type: Sequelize.DATE 34 | }, 35 | createdAt: { 36 | allowNull: false, 37 | type: Sequelize.DATE 38 | }, 39 | updatedAt: { 40 | allowNull: false, 41 | type: Sequelize.DATE 42 | } 43 | }); 44 | }, 45 | down: async (queryInterface, Sequelize) => { 46 | await queryInterface.dropTable('Songs'); 47 | } 48 | }; -------------------------------------------------------------------------------- /server/migrations/20200923122007-create-album.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable('Albums', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | artist_id: { 12 | type: Sequelize.INTEGER 13 | }, 14 | name: { 15 | type: Sequelize.STRING 16 | }, 17 | cover_img: { 18 | type: Sequelize.TEXT 19 | }, 20 | createdAt: { 21 | allowNull: false, 22 | type: Sequelize.DATE 23 | }, 24 | updatedAt: { 25 | allowNull: false, 26 | type: Sequelize.DATE 27 | } 28 | }); 29 | }, 30 | down: async (queryInterface, Sequelize) => { 31 | await queryInterface.dropTable('Albums'); 32 | } 33 | }; -------------------------------------------------------------------------------- /server/migrations/20200923122208-create-artist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable('Artists', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | name: { 12 | type: Sequelize.STRING 13 | }, 14 | description: { 15 | type: Sequelize.TEXT 16 | }, 17 | uploaded_at: { 18 | type: Sequelize.DATE 19 | }, 20 | cover_img: { 21 | type: Sequelize.TEXT 22 | }, 23 | createdAt: { 24 | allowNull: false, 25 | type: Sequelize.DATE 26 | }, 27 | updatedAt: { 28 | allowNull: false, 29 | type: Sequelize.DATE 30 | } 31 | }); 32 | }, 33 | down: async (queryInterface, Sequelize) => { 34 | await queryInterface.dropTable('Artists'); 35 | } 36 | }; -------------------------------------------------------------------------------- /server/migrations/20200923122510-create-interaction.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable('Interactions', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | user_id: { 12 | type: Sequelize.INTEGER 13 | }, 14 | song_id: { 15 | type: Sequelize.INTEGER 16 | }, 17 | is_liked: { 18 | type: Sequelize.BOOLEAN 19 | }, 20 | play_count: { 21 | type: Sequelize.INTEGER 22 | }, 23 | createdAt: { 24 | allowNull: false, 25 | type: Sequelize.DATE 26 | }, 27 | updatedAt: { 28 | allowNull: false, 29 | type: Sequelize.DATE 30 | } 31 | }); 32 | }, 33 | down: async (queryInterface, Sequelize) => { 34 | await queryInterface.dropTable('Interactions'); 35 | } 36 | }; -------------------------------------------------------------------------------- /server/migrations/20200923122629-create-playlists-song.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable('Playlists_Songs', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | playlist_id: { 12 | type: Sequelize.INTEGER 13 | }, 14 | song_id: { 15 | type: Sequelize.INTEGER 16 | }, 17 | createdAt: { 18 | allowNull: false, 19 | type: Sequelize.DATE 20 | }, 21 | updatedAt: { 22 | allowNull: false, 23 | type: Sequelize.DATE 24 | } 25 | }); 26 | }, 27 | down: async (queryInterface, Sequelize) => { 28 | await queryInterface.dropTable('Playlists_Songs'); 29 | } 30 | }; -------------------------------------------------------------------------------- /server/migrations/20200923122713-create-playlist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable('Playlists', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | name: { 12 | type: Sequelize.STRING 13 | }, 14 | uploaded_at: { 15 | type: Sequelize.DATE 16 | }, 17 | cover_img: { 18 | type: Sequelize.TEXT 19 | }, 20 | createdAt: { 21 | allowNull: false, 22 | type: Sequelize.DATE 23 | }, 24 | updatedAt: { 25 | allowNull: false, 26 | type: Sequelize.DATE 27 | } 28 | }); 29 | }, 30 | down: async (queryInterface, Sequelize) => { 31 | await queryInterface.dropTable('Playlists'); 32 | } 33 | }; -------------------------------------------------------------------------------- /server/migrations/20200923123105-create-user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable('Users', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | name: { 12 | type: Sequelize.STRING 13 | }, 14 | email: { 15 | type: Sequelize.STRING 16 | }, 17 | password: { 18 | type: Sequelize.STRING 19 | }, 20 | is_admin: { 21 | type: Sequelize.BOOLEAN 22 | }, 23 | prefrences: { 24 | type: Sequelize.JSON 25 | }, 26 | remember_token: { 27 | type: Sequelize.BOOLEAN 28 | }, 29 | createdAt: { 30 | allowNull: false, 31 | type: Sequelize.DATE 32 | }, 33 | updatedAt: { 34 | allowNull: false, 35 | type: Sequelize.DATE 36 | } 37 | }); 38 | }, 39 | down: async (queryInterface, Sequelize) => { 40 | await queryInterface.dropTable('Users'); 41 | } 42 | }; -------------------------------------------------------------------------------- /server/migrations/20200923123922-create-user-album.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable('User_albums', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | email: { 12 | type: Sequelize.STRING 13 | }, 14 | album_id: { 15 | type: Sequelize.INTEGER 16 | }, 17 | createdAt: { 18 | allowNull: false, 19 | type: Sequelize.DATE 20 | }, 21 | updatedAt: { 22 | allowNull: false, 23 | type: Sequelize.DATE 24 | } 25 | }); 26 | }, 27 | down: async (queryInterface, Sequelize) => { 28 | await queryInterface.dropTable('User_albums'); 29 | } 30 | }; -------------------------------------------------------------------------------- /server/migrations/20200923124017-create-user-artist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable('User_artists', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | email: { 12 | type: Sequelize.STRING 13 | }, 14 | artist_id: { 15 | type: Sequelize.INTEGER 16 | }, 17 | createdAt: { 18 | allowNull: false, 19 | type: Sequelize.DATE 20 | }, 21 | updatedAt: { 22 | allowNull: false, 23 | type: Sequelize.DATE 24 | } 25 | }); 26 | }, 27 | down: async (queryInterface, Sequelize) => { 28 | await queryInterface.dropTable('User_artists'); 29 | } 30 | }; -------------------------------------------------------------------------------- /server/migrations/20200923124102-create-user-playlist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable('User_playlists', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | email: { 12 | type: Sequelize.STRING 13 | }, 14 | playlist_id: { 15 | type: Sequelize.INTEGER 16 | }, 17 | createdAt: { 18 | allowNull: false, 19 | type: Sequelize.DATE 20 | }, 21 | updatedAt: { 22 | allowNull: false, 23 | type: Sequelize.DATE 24 | } 25 | }); 26 | }, 27 | down: async (queryInterface, Sequelize) => { 28 | await queryInterface.dropTable('User_playlists'); 29 | } 30 | }; -------------------------------------------------------------------------------- /server/migrations/20200923124239-create-user-song.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable('User_songs', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | email: { 12 | type: Sequelize.STRING 13 | }, 14 | song_id: { 15 | type: Sequelize.INTEGER 16 | }, 17 | createdAt: { 18 | allowNull: false, 19 | type: Sequelize.DATE 20 | }, 21 | updatedAt: { 22 | allowNull: false, 23 | type: Sequelize.DATE 24 | } 25 | }); 26 | }, 27 | down: async (queryInterface, Sequelize) => { 28 | await queryInterface.dropTable('User_songs'); 29 | } 30 | }; -------------------------------------------------------------------------------- /server/migrations/20200923133639-types-songs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.changeColumn("songs", "album_id", { 6 | type: Sequelize.INTEGER, 7 | allowNull: false 8 | }) 9 | await queryInterface.changeColumn("songs", "artist_id", { 10 | type: Sequelize.INTEGER, 11 | allowNull: false 12 | }) 13 | await queryInterface.changeColumn("songs", "name", { 14 | type: Sequelize.STRING, 15 | allowNull: false 16 | }) 17 | }, 18 | 19 | down: async (queryInterface, Sequelize) => { 20 | await queryInterface.changeColumn("songs", "album_id", { 21 | type: Sequelize.INTEGER, 22 | allowNull: true 23 | }) 24 | await queryInterface.changeColumn("songs", "artist_id", { 25 | type: Sequelize.INTEGER, 26 | allowNull: true 27 | }) 28 | await queryInterface.changeColumn("songs", "name", { 29 | type: Sequelize.STRING, 30 | allowNull: true 31 | }) 32 | } 33 | }; -------------------------------------------------------------------------------- /server/migrations/20200923140433-types-iteractionss.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | /** 6 | * Add altering commands here. 7 | * 8 | * Example: 9 | * await queryInterface.createTable('users', { id: Sequelize.INTEGER }); 10 | */ 11 | 12 | await queryInterface.changeColumn("interactions","user_id",{ 13 | type:Sequelize.INTEGER, 14 | allowNull:false 15 | }) 16 | await queryInterface.changeColumn("interactions","song_id",{ 17 | type:Sequelize.INTEGER, 18 | allowNull:false 19 | }) 20 | await queryInterface.changeColumn("interactions","is_liked",{ 21 | type:Sequelize.BOOLEAN, 22 | defaultValue:0 23 | }) 24 | await queryInterface.changeColumn("interactions","play_count",{ 25 | type:Sequelize.INTEGER, 26 | defaultValue:0 27 | }) 28 | }, 29 | 30 | down: async (queryInterface, Sequelize) => { 31 | /** 32 | * Add reverting commands here. 33 | * 34 | * Example: 35 | * await queryInterface.dropTable('users'); 36 | */ 37 | await queryInterface.changeColumn("interactions","user_id",{ 38 | type:Sequelize.INTEGER, 39 | allowNull:true 40 | }) 41 | await queryInterface.changeColumn("interactions","song_id",{ 42 | type:Sequelize.INTEGER, 43 | allowNull:true 44 | }) 45 | await queryInterface.changeColumn("interactions","is_liked",{ 46 | type:Sequelize.BOOLEAN, 47 | }) 48 | await queryInterface.changeColumn("interactions","play_count",{ 49 | type:Sequelize.INTEGER, 50 | }) 51 | } 52 | }; -------------------------------------------------------------------------------- /server/migrations/20200923140707-types-users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.changeColumn("users","name",{ 6 | type:Sequelize.STRING, 7 | allowNull:false 8 | }) 9 | await queryInterface.changeColumn("users","email",{ 10 | type:Sequelize.STRING, 11 | allowNull:false, 12 | unique: true, 13 | }) 14 | await queryInterface.changeColumn("users","is_admin",{ 15 | type:Sequelize.BOOLEAN, 16 | defaultValue: false 17 | }) 18 | await queryInterface.changeColumn("users","password",{ 19 | type:Sequelize.STRING, 20 | allowNull:false, 21 | min:{ 22 | args:6, 23 | msg:"Minimum 6 characters required in password" 24 | } 25 | }) 26 | await queryInterface.changeColumn("users","remember_token",{ 27 | type:Sequelize.BOOLEAN, 28 | defaultValue: false 29 | }) 30 | 31 | }, 32 | 33 | down: async (queryInterface, Sequelize) => { 34 | await queryInterface.changeColumn("users","name",{ 35 | type:Sequelize.STRING, 36 | allowNull:true 37 | }) 38 | await queryInterface.changeColumn("users","email",{ 39 | type:Sequelize.STRING, 40 | allowNull:true, 41 | unique: false, 42 | }) 43 | await queryInterface.changeColumn("users","is_admin",{ 44 | type:Sequelize.BOOLEAN, 45 | }) 46 | await queryInterface.changeColumn("users","password",{ 47 | type:Sequelize.STRING, 48 | allowNull:true, 49 | 50 | }) 51 | await queryInterface.changeColumn("users","remember_token",{ 52 | type:Sequelize.BOOLEAN, 53 | }) 54 | } 55 | }; -------------------------------------------------------------------------------- /server/migrations/20200923140853-types-albums.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | /** 6 | * Add altering commands here. 7 | * 8 | * Example: 9 | * await queryInterface.createTable('users', { id: Sequelize.INTEGER }); 10 | */ 11 | await queryInterface.changeColumn("albums","artist_id",{ 12 | type:Sequelize.INTEGER, 13 | allowNull:false 14 | }) 15 | await queryInterface.changeColumn("albums","name",{ 16 | type:Sequelize.STRING, 17 | allowNull:false 18 | }) 19 | }, 20 | 21 | down: async (queryInterface, Sequelize) => { 22 | /** 23 | * Add reverting commands here. 24 | * 25 | * Example: 26 | * await queryInterface.dropTable('users'); 27 | */ 28 | await queryInterface.changeColumn("albums","artist_id",{ 29 | type:Sequelize.INTEGER, 30 | allowNull:true 31 | }) 32 | await queryInterface.changeColumn("albums","name",{ 33 | type:Sequelize.STRING, 34 | allowNull:true 35 | }) 36 | } 37 | }; -------------------------------------------------------------------------------- /server/migrations/20200923140957-types-artists.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | /** 6 | * Add altering commands here. 7 | * 8 | * Example: 9 | * await queryInterface.createTable('users', { id: Sequelize.INTEGER }); 10 | */ 11 | await queryInterface.changeColumn("artists","name",{ 12 | type:Sequelize.STRING, 13 | allowNull:false 14 | }) 15 | }, 16 | 17 | down: async (queryInterface, Sequelize) => { 18 | /** 19 | * Add reverting commands here. 20 | * 21 | * Example: 22 | * await queryInterface.dropTable('users'); 23 | */ 24 | await queryInterface.changeColumn("artists","name",{ 25 | type:Sequelize.STRING, 26 | allowNull:true 27 | }) 28 | } 29 | }; -------------------------------------------------------------------------------- /server/migrations/20200923141030-types-playlists.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.changeColumn("playlists","name",{ 6 | type:Sequelize.STRING, 7 | allowNull:false 8 | }) 9 | 10 | }, 11 | 12 | down: async (queryInterface, Sequelize) => { 13 | await queryInterface.changeColumn("playlists","name",{ 14 | type:Sequelize.STRING, 15 | allowNull:true 16 | }) 17 | } 18 | }; -------------------------------------------------------------------------------- /server/migrations/20200923141057-types-playlists_songs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.changeColumn("playlists_songs","playlist_id",{ 6 | type:Sequelize.INTEGER, 7 | allowNull:false 8 | }) 9 | await queryInterface.changeColumn("playlists_songs","song_id",{ 10 | type:Sequelize.INTEGER, 11 | allowNull:false 12 | }) 13 | }, 14 | 15 | down: async (queryInterface, Sequelize) => { 16 | await queryInterface.changeColumn("playlists_songs","playlist_id",{ 17 | type:Sequelize.INTEGER, 18 | allowNull:true 19 | }) 20 | await queryInterface.changeColumn("playlists_songs","song_id",{ 21 | type:Sequelize.INTEGER, 22 | allowNull:true 23 | }) 24 | } 25 | }; -------------------------------------------------------------------------------- /server/migrations/20200923141201-types-user_albums.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.changeColumn("user_albums","email",{ 6 | type:Sequelize.STRING, 7 | allowNull:false 8 | }) 9 | await queryInterface.changeColumn("user_albums","album_id",{ 10 | type:Sequelize.INTEGER, 11 | allowNull:false 12 | }) 13 | }, 14 | 15 | down: async (queryInterface, Sequelize) => { 16 | await queryInterface.changeColumn("user_albums","email",{ 17 | type:Sequelize.STRING, 18 | allowNull:true 19 | }) 20 | await queryInterface.changeColumn("user_albums","album_id",{ 21 | type:Sequelize.INTEGER, 22 | allowNull:true 23 | }) 24 | } 25 | }; -------------------------------------------------------------------------------- /server/migrations/20200923141421-types-user_artists.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.changeColumn("user_artists","email",{ 6 | type:Sequelize.STRING, 7 | allowNull:false 8 | }) 9 | await queryInterface.changeColumn("user_artists","artist_id",{ 10 | type:Sequelize.INTEGER, 11 | allowNull:false 12 | }) 13 | }, 14 | 15 | down: async (queryInterface, Sequelize) => { 16 | await queryInterface.changeColumn("user_artists","email",{ 17 | type:Sequelize.STRING, 18 | allowNull:true 19 | }) 20 | await queryInterface.changeColumn("user_artists","artist_id",{ 21 | type:Sequelize.INTEGER, 22 | allowNull:true 23 | }) 24 | } 25 | }; -------------------------------------------------------------------------------- /server/migrations/20200923141527-types-user_playlists.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.changeColumn("user_playlists","email",{ 6 | type:Sequelize.STRING, 7 | allowNull:false 8 | }) 9 | await queryInterface.changeColumn("user_playlists","playlist_id",{ 10 | type:Sequelize.INTEGER, 11 | allowNull:false 12 | }) 13 | }, 14 | 15 | down: async (queryInterface, Sequelize) => { 16 | await queryInterface.changeColumn("user_playlists","email",{ 17 | type:Sequelize.STRING, 18 | allowNull:true 19 | }) 20 | await queryInterface.changeColumn("user_playlists","playlist_id",{ 21 | type:Sequelize.INTEGER, 22 | allowNull:true 23 | }) 24 | } 25 | }; -------------------------------------------------------------------------------- /server/migrations/20200923141632-types-user_songs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.changeColumn("user_songs","email",{ 6 | type:Sequelize.STRING, 7 | allowNull:false 8 | }) 9 | await queryInterface.changeColumn("user_songs","song_id",{ 10 | type:Sequelize.INTEGER, 11 | allowNull:false 12 | }) 13 | }, 14 | 15 | down: async (queryInterface, Sequelize) => { 16 | await queryInterface.changeColumn("user_songs","email",{ 17 | type:Sequelize.STRING, 18 | allowNull:true 19 | }) 20 | await queryInterface.changeColumn("user_songs","song_id",{ 21 | type:Sequelize.INTEGER, 22 | allowNull:true 23 | }) 24 | } 25 | }; -------------------------------------------------------------------------------- /server/migrations/20200923163228-songs_type.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.changeColumn("songs", "name", { 6 | type: Sequelize.STRING, 7 | allowNull: false 8 | }) 9 | }, 10 | 11 | down: async (queryInterface, Sequelize) => { 12 | await queryInterface.changeColumn("songs", "name", { 13 | type: Sequelize.INTEGER, 14 | allowNull: true 15 | }) 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /server/migrations/20200924211319-paranoid.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.addColumn( 6 | "songs", 7 | 'deletedAt', 8 | { 9 | type: Sequelize.DATE, 10 | allowNull: true, 11 | validate: { 12 | } 13 | } 14 | ); 15 | await queryInterface.addColumn( 16 | "albums", 17 | 'deletedAt', 18 | { 19 | type: Sequelize.DATE, 20 | allowNull: true, 21 | validate: { 22 | } 23 | } 24 | ) 25 | await queryInterface.addColumn( 26 | "artists", 27 | 'deletedAt', 28 | { 29 | type: Sequelize.DATE, 30 | allowNull: true, 31 | validate: { 32 | } 33 | } 34 | ) 35 | await queryInterface.addColumn( 36 | "interactions", 37 | 'deletedAt', 38 | { 39 | type: Sequelize.DATE, 40 | allowNull: true, 41 | validate: { 42 | } 43 | }) 44 | await queryInterface.addColumn( 45 | "playlists", 46 | 'deletedAt', 47 | { 48 | type: Sequelize.DATE, 49 | allowNull: true, 50 | validate: { 51 | } 52 | }) 53 | await queryInterface.addColumn( 54 | "playlists_songs", 55 | 'deletedAt', 56 | { 57 | type: Sequelize.DATE, 58 | allowNull: true, 59 | validate: { 60 | } 61 | }) 62 | await queryInterface.addColumn( 63 | "users", 64 | 'deletedAt', 65 | { 66 | type: Sequelize.DATE, 67 | allowNull: true, 68 | validate: { 69 | } 70 | }) 71 | await queryInterface.addColumn( 72 | "user_albums", 73 | 'deletedAt', 74 | { 75 | type: Sequelize.DATE, 76 | allowNull: true, 77 | validate: { 78 | } 79 | }) 80 | 81 | await queryInterface.addColumn( 82 | "user_artists", 83 | 'deletedAt', 84 | { 85 | type: Sequelize.DATE, 86 | allowNull: true, 87 | validate: { 88 | } 89 | }) 90 | await queryInterface.addColumn( 91 | "user_playlists", 92 | 'deletedAt', 93 | { 94 | type: Sequelize.DATE, 95 | allowNull: true, 96 | validate: { 97 | } 98 | }) 99 | await queryInterface.addColumn( 100 | "user_songs", 101 | 'deletedAt', 102 | { 103 | type: Sequelize.DATE, 104 | allowNull: true, 105 | validate: { 106 | } 107 | }) 108 | }, 109 | 110 | down: async (queryInterface, Sequelize) => { 111 | await queryInterface.removeColumn('songs', 'deletedAt'); 112 | await queryInterface.removeColumn('albums', 'deletedAt'); 113 | await queryInterface.removeColumn('artists', 'deletedAt'); 114 | await queryInterface.removeColumn('interactions', 'deletedAt'); 115 | await queryInterface.removeColumn('playlists', 'deletedAt'); 116 | await queryInterface.removeColumn('playlists_songs', 'deletedAt'); 117 | await queryInterface.removeColumn('user_albums', 'deletedAt'); 118 | await queryInterface.removeColumn('user_artists', 'deletedAt'); 119 | await queryInterface.removeColumn('user_playlists', 'deletedAt'); 120 | await queryInterface.removeColumn('user_songs', 'deletedAt'); 121 | await queryInterface.removeColumn('user_users', 'deletedAt'); 122 | } 123 | }; -------------------------------------------------------------------------------- /server/migrations/20200924211637-underscore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.renameColumn("albums","createdAt","created_at") 6 | await queryInterface.renameColumn("albums","updatedAt","updated_at") 7 | await queryInterface.renameColumn("albums","deletedAt","deleted_at") 8 | await queryInterface.renameColumn("artists","createdAt","created_at") 9 | await queryInterface.renameColumn("artists","updatedAt","updated_at") 10 | await queryInterface.renameColumn("artists","deletedAt","deleted_at") 11 | await queryInterface.renameColumn("interactions","createdAt","created_at") 12 | await queryInterface.renameColumn("interactions","updatedAt","updated_at") 13 | await queryInterface.renameColumn("interactions","deletedAt","deleted_at") 14 | await queryInterface.renameColumn("playlists","createdAt","created_at") 15 | await queryInterface.renameColumn("playlists","updatedAt","updated_at") 16 | await queryInterface.renameColumn("playlists","deletedAt","deleted_at") 17 | await queryInterface.renameColumn("playlists_songs","createdAt","created_at") 18 | await queryInterface.renameColumn("playlists_songs","updatedAt","updated_at") 19 | await queryInterface.renameColumn("playlists_songs","deletedAt","deleted_at") 20 | await queryInterface.renameColumn("songs","createdAt","created_at") 21 | await queryInterface.renameColumn("songs","updatedAt","updated_at") 22 | await queryInterface.renameColumn("songs","deletedAt","deleted_at") 23 | await queryInterface.renameColumn("users","createdAt","created_at") 24 | await queryInterface.renameColumn("users","updatedAt","updated_at") 25 | await queryInterface.renameColumn("users","deletedAt","deleted_at") 26 | await queryInterface.renameColumn("user_songs","createdAt","created_at") 27 | await queryInterface.renameColumn("user_songs","updatedAt","updated_at") 28 | await queryInterface.renameColumn("user_songs","deletedAt","deleted_at") 29 | await queryInterface.renameColumn("user_albums","createdAt","created_at") 30 | await queryInterface.renameColumn("user_albums","updatedAt","updated_at") 31 | await queryInterface.renameColumn("user_albums","deletedAt","deleted_at") 32 | await queryInterface.renameColumn("user_artists","createdAt","created_at") 33 | await queryInterface.renameColumn("user_artists","updatedAt","updated_at") 34 | await queryInterface.renameColumn("user_artists","deletedAt","deleted_at") 35 | await queryInterface.renameColumn("user_playlists","createdAt","created_at") 36 | await queryInterface.renameColumn("user_playlists","updatedAt","updated_at") 37 | await queryInterface.renameColumn("user_playlists","deletedAt","deleted_at") 38 | }, 39 | 40 | down: async (queryInterface, Sequelize) => { 41 | await queryInterface.renameColumn("artists","created_at","createdAt") 42 | await queryInterface.renameColumn("artists","updated_at","updatedAt") 43 | await queryInterface.renameColumn("artists","deleted_at","deletedAt") 44 | await queryInterface.renameColumn("albums","created_at","createdAt") 45 | await queryInterface.renameColumn("albums","updated_at","updatedAt") 46 | await queryInterface.renameColumn("albums","deleted_at","deletedAt") 47 | await queryInterface.renameColumn("interactions","created_at","createdAt") 48 | await queryInterface.renameColumn("interactions","updated_at","updatedAt") 49 | await queryInterface.renameColumn("interactions","deleted_at","deletedAt") 50 | await queryInterface.renameColumn("playlists","created_at","createdAt") 51 | await queryInterface.renameColumn("playlists","updated_at","updatedAt") 52 | await queryInterface.renameColumn("playlists","deleted_at","deletedAt") 53 | await queryInterface.renameColumn("playlists_songs","created_at","createdAt") 54 | await queryInterface.renameColumn("playlists_songs","updated_at","updatedAt") 55 | await queryInterface.renameColumn("playlists_songs","deleted_at","deletedAt") 56 | await queryInterface.renameColumn("songs","created_at","createdAt") 57 | await queryInterface.renameColumn("songs","updated_at","updatedAt") 58 | await queryInterface.renameColumn("songs","deleted_at","deletedAt") 59 | await queryInterface.renameColumn("users","created_at","createdAt") 60 | await queryInterface.renameColumn("users","updated_at","updatedAt") 61 | await queryInterface.renameColumn("users","deleted_at","deletedAt") 62 | await queryInterface.renameColumn("user_songs","created_at","createdAt") 63 | await queryInterface.renameColumn("user_songs","updated_at","updatedAt") 64 | await queryInterface.renameColumn("user_songs","deleted_at","deletedAt") 65 | await queryInterface.renameColumn("user_albums","created_at","createdAt") 66 | await queryInterface.renameColumn("user_albums","updated_at","updatedAt") 67 | await queryInterface.renameColumn("user_albums","deleted_at","deletedAt") 68 | await queryInterface.renameColumn("user_artists","created_at","createdAt") 69 | await queryInterface.renameColumn("user_artists","updated_at","updatedAt") 70 | await queryInterface.renameColumn("user_artists","deleted_at","deletedAt") 71 | await queryInterface.renameColumn("user_playlists","created_at","createdAt") 72 | await queryInterface.renameColumn("user_playlists","updated_at","updatedAt") 73 | await queryInterface.renameColumn("user_playlists","deleted_at","deletedAt") 74 | } 75 | }; -------------------------------------------------------------------------------- /server/migrations/20200927040244-add-isLiken-columns.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: async (queryInterface, Sequelize) => { 5 | await queryInterface.addColumn( 6 | "user_albums", 7 | 'is_Liked', 8 | { 9 | type: Sequelize.BOOLEAN, 10 | allowNull: true, 11 | validate: { 12 | } 13 | }) 14 | 15 | await queryInterface.addColumn( 16 | "user_artists", 17 | 'is_Liked', 18 | { 19 | type: Sequelize.BOOLEAN, 20 | allowNull: true, 21 | validate: { 22 | } 23 | }) 24 | await queryInterface.addColumn( 25 | "user_playlists", 26 | 'is_Liked', 27 | { 28 | type: Sequelize.BOOLEAN, 29 | allowNull: true, 30 | validate: { 31 | } 32 | }) 33 | await queryInterface.addColumn( 34 | "user_songs", 35 | 'is_Liked', 36 | { 37 | type: Sequelize.BOOLEAN, 38 | allowNull: true, 39 | validate: { 40 | } 41 | }) 42 | }, 43 | 44 | down: async (queryInterface, Sequelize) => { 45 | await queryInterface.removeColumn('user_albums', 'isLiked'); 46 | await queryInterface.removeColumn('user_artists', 'isLiked'); 47 | await queryInterface.removeColumn('user_playlists', 'isLiked'); 48 | await queryInterface.removeColumn('user_songs', 'isLiked'); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /server/models/album.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { 3 | Model 4 | } = require('sequelize'); 5 | module.exports = (sequelize, DataTypes) => { 6 | class Album extends Model { 7 | /** 8 | * Helper method for defining associations. 9 | * This method is not a part of Sequelize lifecycle. 10 | * The `models/index` file will call this method automatically. 11 | */ 12 | static associate(models) { 13 | this.hasMany(models.Song, { 14 | foreignKey: 'albumId', 15 | 16 | }) 17 | this.hasMany(models.User_album, { 18 | foreignKey: 'albumId', 19 | 20 | }) 21 | this.belongsTo(models.Artist, { 22 | foreignKey: "artistId" 23 | }) 24 | 25 | } 26 | }; 27 | Album.init({ 28 | artistId: { 29 | field: 'artist_id', 30 | type: DataTypes.STRING 31 | }, 32 | name: DataTypes.STRING, 33 | coverImg: { 34 | field: 'cover_img', 35 | type: DataTypes.TEXT 36 | } 37 | }, { 38 | sequelize, 39 | modelName: 'Album', 40 | paranoid: true 41 | }); 42 | return Album; 43 | }; -------------------------------------------------------------------------------- /server/models/artist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { 3 | Model 4 | } = require('sequelize'); 5 | module.exports = (sequelize, DataTypes) => { 6 | class Artist extends Model { 7 | /** 8 | * Helper method for defining associations. 9 | * This method is not a part of Sequelize lifecycle. 10 | * The `models/index` file will call this method automatically. 11 | */ 12 | static associate(models) { 13 | this.hasMany(models.Song, { 14 | foreignKey: 'artistId', 15 | 16 | }) 17 | this.hasMany(models.Album, { 18 | foreignKey: 'artistId', 19 | 20 | }) 21 | this.hasMany(models.User_artist, { 22 | foreignKey: 'artistId', 23 | 24 | }) 25 | 26 | 27 | } 28 | }; 29 | Artist.init({ 30 | name: DataTypes.STRING, 31 | uploadedAt:{ 32 | field:'uploaded_at', 33 | type: DataTypes.DATE 34 | }, 35 | coverImg:{ 36 | field:'cover_img', 37 | type: DataTypes.TEXT 38 | } 39 | }, { 40 | sequelize, 41 | modelName: 'Artist', 42 | paranoid: true 43 | }); 44 | return Artist; 45 | }; -------------------------------------------------------------------------------- /server/models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const Sequelize = require('sequelize'); 6 | const basename = path.basename(__filename); 7 | const env = process.env.NODE_ENV || 'development'; 8 | const config = require(__dirname + '/../config/config.js')[env]; 9 | const db = {}; 10 | 11 | let sequelize; 12 | if (config.use_env_variable) { 13 | sequelize = new Sequelize(process.env[config.use_env_variable], config); 14 | } else { 15 | sequelize = new Sequelize(config.database, config.username, config.password, config); 16 | } 17 | 18 | fs 19 | .readdirSync(__dirname) 20 | .filter(file => { 21 | return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); 22 | }) 23 | .forEach(file => { 24 | const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes); 25 | db[model.name] = model; 26 | }); 27 | 28 | Object.keys(db).forEach(modelName => { 29 | if (db[modelName].associate) { 30 | db[modelName].associate(db); 31 | } 32 | }); 33 | 34 | db.sequelize = sequelize; 35 | db.Sequelize = Sequelize; 36 | 37 | module.exports = db; 38 | -------------------------------------------------------------------------------- /server/models/interaction.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { 3 | Model 4 | } = require('sequelize'); 5 | module.exports = (sequelize, DataTypes) => { 6 | class Interaction extends Model { 7 | /** 8 | * Helper method for defining associations. 9 | * This method is not a part of Sequelize lifecycle. 10 | * The `models/index` file will call this method automatically. 11 | */ 12 | static associate(models) { 13 | this.belongsTo(models.Song, { 14 | foreignKey: 'songId' 15 | }); 16 | this.belongsTo(models.User, { 17 | foreignKey: 'songId' 18 | }); 19 | } 20 | }; 21 | Interaction.init({ 22 | userId: { 23 | field: "user_id", 24 | type: DataTypes.INTEGER 25 | }, 26 | songId: { 27 | field: "song_id", 28 | type: DataTypes.INTEGER 29 | }, 30 | isLiked: { 31 | field: "is_liked", 32 | type: DataTypes.BOOLEAN, 33 | }, 34 | playCount: { 35 | field: "play_count", 36 | type: DataTypes.INTEGER 37 | } 38 | }, { 39 | sequelize, 40 | modelName: 'Interaction', 41 | paranoid: true 42 | }); 43 | return Interaction; 44 | }; -------------------------------------------------------------------------------- /server/models/playlist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { 3 | Model 4 | } = require('sequelize'); 5 | module.exports = (sequelize, DataTypes) => { 6 | class Playlist extends Model { 7 | /** 8 | * Helper method for defining associations. 9 | * This method is not a part of Sequelize lifecycle. 10 | * The `models/index` file will call this method automatically. 11 | */ 12 | static associate(models) { 13 | this.hasMany(models.PlaylistsSong, { 14 | foreignKey: 'playlistId', 15 | }) 16 | this.hasMany(models.User_playlist, { 17 | foreignKey: 'playlistId', 18 | }) 19 | } 20 | }; 21 | Playlist.init({ 22 | name: DataTypes.STRING, 23 | uploadedAt: { 24 | field: "uploaded_at", 25 | type: DataTypes.DATE 26 | }, 27 | coverImg: { 28 | field: 'cover_img', 29 | type: DataTypes.TEXT 30 | } 31 | }, { 32 | sequelize, 33 | modelName: 'Playlist', 34 | paranoid: true 35 | }); 36 | return Playlist; 37 | }; -------------------------------------------------------------------------------- /server/models/playlists_song.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { 3 | Model 4 | } = require('sequelize'); 5 | module.exports = (sequelize, DataTypes) => { 6 | class Playlists_Song extends Model { 7 | /** 8 | * Helper method for defining associations. 9 | * This method is not a part of Sequelize lifecycle. 10 | * The `models/index` file will call this method automatically. 11 | */ 12 | static associate(models) { 13 | this.belongsTo(models.Song, { 14 | foreignKey: 'songId' 15 | }); 16 | this.belongsTo(models.Playlist, { 17 | foreignKey: 'playlistId' 18 | }); 19 | } 20 | }; 21 | Playlists_Song.init({ 22 | playlistId: { 23 | field: "playlist_id", 24 | type: DataTypes.INTEGER 25 | }, 26 | songId: { 27 | field: "song_id", 28 | type: DataTypes.INTEGER 29 | } 30 | }, { 31 | sequelize, 32 | modelName: 'PlaylistsSong', 33 | paranoid: true 34 | }); 35 | return Playlists_Song; 36 | }; -------------------------------------------------------------------------------- /server/models/song.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { 3 | Model 4 | } = require('sequelize'); 5 | module.exports = (sequelize, DataTypes) => { 6 | class Song extends Model { 7 | /** 8 | * Helper method for defining associations. 9 | * This method is not a part of Sequelize lifecycle. 10 | * The `models/index` file will call this method automatically. 11 | */ 12 | static associate(models) { 13 | this.hasMany(models.PlaylistsSong, { 14 | foreignKey: "songId" 15 | }); 16 | this.hasMany(models.Interaction, { 17 | foreignKey: 'songId' 18 | }); 19 | this.hasMany(models.User_song, { 20 | foreignKey: 'songId' 21 | }); 22 | this.belongsTo(models.Artist, { 23 | foreignKey: 'artistId' 24 | }); 25 | this.belongsTo(models.Album, { 26 | foreignKey: 'albumId' 27 | }); 28 | 29 | } 30 | }; 31 | Song.init({ 32 | youtubeLink: { 33 | field: "youtube_link", 34 | type: DataTypes.STRING 35 | }, 36 | albumId: { 37 | field: "album_id", 38 | type: DataTypes.INTEGER 39 | }, 40 | artistId: { 41 | field: "artist_id", 42 | type: DataTypes.INTEGER 43 | }, 44 | name: DataTypes.STRING, 45 | length: DataTypes.TIME, 46 | trackNumber: { 47 | field: "track_number", 48 | type: DataTypes.INTEGER 49 | }, 50 | lyrics: DataTypes.TEXT, 51 | uploadAt: { 52 | field: "upload_at", 53 | type: DataTypes.DATE 54 | } 55 | }, { 56 | sequelize, 57 | modelName: 'Song', 58 | paranoid: true 59 | }); 60 | return Song; 61 | }; -------------------------------------------------------------------------------- /server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { 3 | Model 4 | } = require('sequelize'); 5 | module.exports = (sequelize, DataTypes) => { 6 | class User extends Model { 7 | /** 8 | * Helper method for defining associations. 9 | * This method is not a part of Sequelize lifecycle. 10 | * The `models/index` file will call this method automatically. 11 | */ 12 | static associate(models) { 13 | this.hasMany(models.User_album, { 14 | foreignKey: "email" 15 | }) 16 | this.hasMany(models.User_playlist, { 17 | foreignKey: "email" 18 | }) 19 | this.hasMany(models.User_artist, { 20 | foreignKey: "email" 21 | }) 22 | this.hasMany(models.User_song, { 23 | foreignKey: "email" 24 | }) 25 | this.hasMany(models.Interaction, { 26 | foreignKey: 'userId' 27 | }) 28 | 29 | } 30 | }; 31 | User.init({ 32 | name: DataTypes.STRING, 33 | email: DataTypes.STRING, 34 | password: DataTypes.STRING, 35 | isAdmin: { 36 | field: "is_admin", 37 | type: DataTypes.BOOLEAN 38 | }, 39 | prefrences: DataTypes.JSON, 40 | rememberToken: { 41 | field: "remember_token", 42 | type: DataTypes.BOOLEAN 43 | } 44 | }, { 45 | sequelize, 46 | modelName: 'User', 47 | paranoid: true 48 | }); 49 | return User; 50 | }; -------------------------------------------------------------------------------- /server/models/user_album.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { 3 | Model 4 | } = require('sequelize'); 5 | module.exports = (sequelize, DataTypes) => { 6 | class User_album extends Model { 7 | /** 8 | * Helper method for defining associations. 9 | * This method is not a part of Sequelize lifecycle. 10 | * The `models/index` file will call this method automatically. 11 | */ 12 | static associate(models) { 13 | this.belongsTo(models.Album, { 14 | foreignKey: "albumId" 15 | }) 16 | this.belongsTo(models.User, { 17 | foreignKey: "email" 18 | }) 19 | 20 | } 21 | }; 22 | User_album.init({ 23 | email: DataTypes.STRING, 24 | albumId: { 25 | field: "album_id", 26 | type: DataTypes.INTEGER 27 | }, 28 | isLiked: { 29 | field: 'is_Liked', 30 | type: DataTypes.BOOLEAN 31 | } 32 | }, { 33 | sequelize, 34 | modelName: 'User_album', 35 | paranoid: true 36 | }); 37 | return User_album; 38 | }; -------------------------------------------------------------------------------- /server/models/user_artist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { 3 | Model 4 | } = require('sequelize'); 5 | module.exports = (sequelize, DataTypes) => { 6 | class User_artist extends Model { 7 | /** 8 | * Helper method for defining associations. 9 | * This method is not a part of Sequelize lifecycle. 10 | * The `models/index` file will call this method automatically. 11 | */ 12 | static associate(models) { 13 | this.belongsTo(models.Artist, { 14 | foreignKey: "artistId" 15 | }) 16 | this.belongsTo(models.User, { 17 | foreignKey: "email" 18 | }) 19 | 20 | } 21 | }; 22 | User_artist.init({ 23 | email: DataTypes.STRING, 24 | artistId: { 25 | field: "artist_id", 26 | type: DataTypes.INTEGER 27 | }, 28 | isLiked: { 29 | field: 'is_Liked', 30 | type: DataTypes.BOOLEAN 31 | } 32 | }, { 33 | sequelize, 34 | modelName: 'User_artist', 35 | paranoid: true 36 | }); 37 | return User_artist; 38 | }; -------------------------------------------------------------------------------- /server/models/user_playlist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { 3 | Model 4 | } = require('sequelize'); 5 | module.exports = (sequelize, DataTypes) => { 6 | class User_playlist extends Model { 7 | /** 8 | * Helper method for defining associations. 9 | * This method is not a part of Sequelize lifecycle. 10 | * The `models/index` file will call this method automatically. 11 | */ 12 | static associate(models) { 13 | this.belongsTo(models.Playlist, { 14 | foreignKey: "playlistId" 15 | }) 16 | this.belongsTo(models.User, { 17 | foreignKey: "email" 18 | }) 19 | 20 | } 21 | }; 22 | User_playlist.init({ 23 | email: DataTypes.STRING, 24 | playlistId: { 25 | field: "playlist_id", 26 | type: DataTypes.INTEGER 27 | }, 28 | isLiked: { 29 | field: 'is_Liked', 30 | type: DataTypes.BOOLEAN 31 | } 32 | }, { 33 | sequelize, 34 | modelName: 'User_playlist', 35 | paranoid: true 36 | }); 37 | return User_playlist; 38 | }; -------------------------------------------------------------------------------- /server/models/user_song.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { 3 | Model 4 | } = require('sequelize'); 5 | module.exports = (sequelize, DataTypes) => { 6 | class User_song extends Model { 7 | /** 8 | * Helper method for defining associations. 9 | * This method is not a part of Sequelize lifecycle. 10 | * The `models/index` file will call this method automatically. 11 | */ 12 | static associate(models) { 13 | this.belongsTo(models.Song, { 14 | foreignKey: "songId" 15 | }) 16 | this.belongsTo(models.User, { 17 | foreignKey: "email" 18 | }) 19 | 20 | } 21 | }; 22 | User_song.init({ 23 | email: DataTypes.STRING, 24 | songId: { 25 | field: "song_id", 26 | type: DataTypes.INTEGER 27 | }, 28 | isLiked: { 29 | field: 'is_Liked', 30 | type: DataTypes.BOOLEAN 31 | } 32 | }, { 33 | sequelize, 34 | modelName: 'User_song', 35 | paranoid: true 36 | }); 37 | return User_song; 38 | }; -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "node_modules/.bin/jest", 8 | "dev": "nodemon start.js", 9 | "start": "node start.js", 10 | "migrate": "node_modules/.bin/sequelize db:migrate", 11 | "seed": "node_modules/.bin/sequelize db:seed:all" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "bcrypt": "^5.0.0", 18 | "dotenv": "^8.2.0", 19 | "express": "^4.17.1", 20 | "jsonwebtoken": "^8.5.1", 21 | "morgan": "^1.10.0", 22 | "mysql": "^2.18.1", 23 | "mysql2": "^2.2.5", 24 | "nodemon": "^2.0.4", 25 | "sequelize": "^6.3.5" 26 | }, 27 | "devDependencies": { 28 | "jest": "^26.4.2", 29 | "sequelize-cli": "^6.2.0", 30 | "supertest": "^4.0.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const express = require('express'); 3 | const app = express(); 4 | 5 | // global middlewares 6 | app.use(express.urlencoded({ extended: true })); 7 | app.use(express.json()); 8 | app.use(require('./helpers/morgan')); 9 | 10 | //users login,register validation 11 | app.use('/users', require('./Routes/users')); 12 | 13 | // check token validation, and updateing infromation about user 14 | app.use(require('./helpers/tokenCheck')); 15 | 16 | // expose api 17 | app.use('/api', require('./api')) 18 | 19 | // handle unknown endpoint 20 | app.use(require('./helpers/unknownEndpoint')); 21 | 22 | // error handling 23 | app.use(require('./helpers/errorHandler')); 24 | 25 | 26 | module.exports = app; 27 | -------------------------------------------------------------------------------- /server/start.js: -------------------------------------------------------------------------------- 1 | const server = require('./server'); 2 | 3 | const PORT = process.env.PORT; 4 | 5 | server.listen(PORT, () => { 6 | console.log(`Server running on port ${PORT}`); 7 | }); 8 | -------------------------------------------------------------------------------- /server/tests/songsInPlaylists.test.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @jest-environment node 4 | */ 5 | 6 | require('dotenv').config(); 7 | const request = require('supertest'); 8 | const server = require('../server'); 9 | const bcrypt = require('bcrypt'); 10 | const { Playlist, PlaylistsSong, Song, Album, Artist, User } = require('../models'); 11 | const { Op } = require("sequelize"); 12 | 13 | const playlistMock = { 14 | name: 'new playlist name', 15 | } 16 | 17 | const artistMock = { 18 | name: 'new artist name', 19 | } 20 | 21 | const albumMock = { 22 | name: 'new album name', 23 | } 24 | 25 | const songMock = { 26 | name: 'new song name', 27 | } 28 | 29 | const songInPlaylistMock = {} 30 | 31 | const userMock = { 32 | name: 'test4', 33 | isAdmin: true, 34 | email: 'test4@test.com', 35 | password: process.env.PASSWORD, 36 | rememberToken: false 37 | } 38 | 39 | let header; 40 | 41 | describe('check songInPlaylist routs', () => { 42 | beforeAll(async () => { 43 | userMock.password = await bcrypt.hash(process.env.PASSWORD, 10); 44 | await User.create(userMock); 45 | 46 | userMock.password = process.env.PASSWORD; 47 | const response = await request(server) 48 | .post("/users/logIn") 49 | .send(userMock) 50 | .expect(200); 51 | 52 | header = response.header; 53 | 54 | const { body: newPlaylist } = await request(server) 55 | .post('/api/v1/playlists') 56 | .set('Authorization', header['authorization']) 57 | .send(playlistMock) 58 | .expect(200); 59 | 60 | songInPlaylistMock.playlistId = newPlaylist.id; 61 | 62 | const { body: newArtist } = await request(server) 63 | .post('/api/v1/artists') 64 | .set('Authorization', header['authorization']) 65 | .send(artistMock) 66 | .expect(200); 67 | 68 | songMock.artistId = newArtist.id; 69 | albumMock.artistId = newArtist.id; 70 | 71 | const { body: newAlbum } = await request(server) 72 | .post('/api/v1/albums') 73 | .set('Authorization', header['authorization']) 74 | .send(albumMock) 75 | .expect(200); 76 | 77 | songMock.albumId = newAlbum.id; 78 | 79 | const { body: newSong } = await request(server) 80 | .post('/api/v1/songs') 81 | .set('Authorization', header['authorization']) 82 | .send(songMock) 83 | .expect(200); 84 | 85 | songInPlaylistMock.songId = newSong.id; 86 | 87 | }) 88 | afterAll(async () => { 89 | await User.destroy({ truncate: true, force: true }); 90 | await Song.destroy({ truncate: true, force: true }); 91 | await Album.destroy({ truncate: true, force: true }); 92 | await Artist.destroy({ truncate: true, force: true }); 93 | await Playlist.destroy({ truncate: true, force: true }); 94 | await server.close(); 95 | }); 96 | afterEach(async () => { 97 | await PlaylistsSong.destroy({ truncate: true, force: true }); 98 | }); 99 | 100 | it("Can add song to playlist", async () => { 101 | const { body: newSongInPlaylist } = await request(server) 102 | .post('/api/v1/songsInPlaylists') 103 | .set('Authorization', header['authorization']) 104 | .send(songInPlaylistMock) 105 | .expect(200); 106 | 107 | expect(newSongInPlaylist.songId).toBe(songInPlaylistMock.songId); 108 | expect(newSongInPlaylist.playlistId).toBe(songInPlaylistMock.playlistId); 109 | const songInPlaylistFromDB = await PlaylistsSong.findOne({ where: { [Op.and]: [{ playlistId: newSongInPlaylist.playlistId }, { songId: newSongInPlaylist.songId }] } }); 110 | expect(songInPlaylistFromDB.songId).toBe(newSongInPlaylist.songId); 111 | expect(songInPlaylistFromDB.playlistId).toBe(newSongInPlaylist.playlistId); 112 | }); 113 | 114 | it('Can delete song from playlist', async () => { 115 | const { body: newSongInPlaylist } = await request(server) 116 | .post('/api/v1/songsInPlaylists') 117 | .set('Authorization', header['authorization']) 118 | .send(songInPlaylistMock) 119 | .expect(200); 120 | 121 | await request(server) 122 | .delete(`/api/v1/songsInPlaylists?playlistId=${newSongInPlaylist.playlistId}&songId=${newSongInPlaylist.songId}`) 123 | .set('Authorization', header['authorization']) 124 | .expect(200); 125 | 126 | const songInPlaylistFromDB = await PlaylistsSong.findOne({ where: { [Op.and]: [{ playlistId: newSongInPlaylist.playlistId }, { songId: newSongInPlaylist.songId }] } }); 127 | expect(songInPlaylistFromDB).toBe(null); 128 | }) 129 | }) -------------------------------------------------------------------------------- /server/tests/users.test.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @jest-environment node 4 | */ 5 | 6 | require('dotenv').config(); 7 | const request = require('supertest'); 8 | const server = require('../server'); 9 | const { User } = require('../models'); 10 | const { Op } = require("sequelize"); 11 | 12 | const userMock = { 13 | name: 'test5', 14 | email: 'test5@test.com', 15 | password: process.env.PASSWORD, 16 | rememberToken: false 17 | } 18 | 19 | describe('check songInUser routs', () => { 20 | afterAll(async () => { 21 | await server.close(); 22 | }); 23 | afterEach(async () => { 24 | await User.destroy({ truncate: true, force: true }); 25 | }); 26 | 27 | it("Can register as user", async () => { 28 | const { body: newUser } = await request(server) 29 | .post('/users/register') 30 | .send(userMock) 31 | .expect(200); 32 | 33 | expect(newUser.name).toBe(userMock.name); 34 | expect(newUser.email).toBe(userMock.email); 35 | const UserFromDB = await User.findOne({ where: { [Op.and]: [{ name: newUser.name }, { email: newUser.email }] } }); 36 | expect(UserFromDB.name).toBe(newUser.name); 37 | expect(UserFromDB.email).toBe(newUser.email); 38 | }); 39 | 40 | it("Can login as user and pass validation", async () => { 41 | const { body: newUser } = await request(server) 42 | .post('/users/register') 43 | .send(userMock) 44 | .expect(200); 45 | 46 | const response = await request(server) 47 | .post("/users/logIn") 48 | .send(userMock) 49 | .expect(200); 50 | 51 | expect(response.body).toBe(`welcome back ${newUser.name}`); 52 | }) 53 | }) --------------------------------------------------------------------------------