├── .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 | #  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 | # 
9 |
10 | my ERD:
11 | # 
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 |
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 |
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 |
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 |
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 |
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 |

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 |

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 |

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 |

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 | -
108 |
109 |
110 |
111 | {song.name}
112 | {albums[0].name}
113 | {artist.name}
114 |
115 |
116 |
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 |
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 |
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 |

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 |

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 | }/0.jpg`})
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 | -
13 |
14 |
15 |
16 |
17 | {song.name}
18 | {song.Album.name}
19 | {song.Artist.name}
20 |
21 |
22 |
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 | })
--------------------------------------------------------------------------------