Based off of the original [Musare](https://github.com/Musare/MusareMeteor), which utilized Meteor.
@@ -16,11 +15,11 @@ You can also find us on [Facebook](https://www.facebook.com/MusareMusic) and [Tw
### Our Stack
-- NodeJS
-- MongoDB
-- Redis
-- Nginx (not required)
-- VueJS
+- NodeJS
+- MongoDB
+- Redis
+- Nginx (not required)
+- VueJS
### Frontend
@@ -36,16 +35,15 @@ We currently only utilize 1 backend, 1 MongoDB server and 1 Redis server running
Installing with Docker: (not recommended for Windows users)
-- [Docker](https://www.docker.com/)
+- [Docker](https://www.docker.com/)
Standard Installation:
-- [NodeJS](https://nodejs.org/en/download/)
- _ nodemon: `yarn global add nodemon`
- _ [node-gyp](https://github.com/nodejs/node-gyp#installation): `yarn global add node-gyp`
-- [Yarn (Windows)](https://yarnpkg.com/lang/en/docs/install/#windows-stable) [Yarn (Unix)](https://yarnpkg.com/lang/en/docs/install/#debian-stable) ([npm](https://www.npmjs.com/) can also be used)
-- [MongoDB](https://www.mongodb.com/download-center) Currently version 4.0
- |`mode`|Should be either `development` or `production`. No more explanation needed.|
- |`secret`|Whatever you want - used by express's session module.|
- |`domain`|Should be the url where the site will be accessible from,usually `http://localhost` for non-Docker.|
- |`serverDomain`|Should be the url where the backend will be accessible from, usually `http://localhost:8080` for non-Docker.|
- |`serverPort`|Should be the port where the backend will listen on, should always be `8080` for Docker, and is recommended for non-Docker.|
- |`isDocker`|Self-explanatory. Are you using Docker?|
- |`serverPort`|Should be the port where the backend will listen on, should always be `8080` for Docker, and is recommended for non-Docker.|
- |`apis.youtube.key`|Can be obtained by setting up a [YouTube API Key](https://developers.google.com/youtube/v3/getting-started). You need to use the YouTube Data API v3, and create an API key.|
- |`apis.recaptcha.secret`|Can be obtained by setting up a [ReCaptcha Site (v3)](https://www.google.com/recaptcha/admin).|
- |`apis.github`|Can be obtained by setting up a [GitHub OAuth Application](https://github.com/settings/developers). You need to fill in some values to create the OAuth application. The homepage is the homepage of frontend. The authorization callback url is the backend url with `/auth/github/authorize/callback` added at the end. For example `http://localhost:8080/auth/github/authorize/callback`.|
- |`apis.discord.token`|Token for the Discord bot.|
- |`apis.discord.loggingServer`|Server ID of the Discord logging server.|
- |`apis.discord.loggingChannel`|ID of the channel to be used in the Discord logging server.|
- |`apis.mailgun`|Can be obtained by setting up a [Mailgun account](http://www.mailgun.com/), or you can disable it.|
- |`apis.spotify`|Can be obtained by setting up a [Spotify client id](https://developer.spotify.com/dashboard/applications), or you can disable it.|
- |`apis.discogs`|Can be obtained by setting up a [Discogs application](https://www.discogs.com/settings/developers), or you can disable it.|
- |`redis.url`|Should be left alone for Docker, and changed to `redis://localhost:6379/0` for non-Docker.|
- |`redis.password`|Should be the Redis password you either put in your `startRedis.cmd` file for Windows, or `.env` for docker.|
- |`mongo.url`|Needs to have the proper password for the MongoDB musare user, and for non-Docker you need to replace `@musare:27017` with `@localhost:27017`.|
- |`cookie.domain`|Should be the ip or address you use to access the site, without protocols (http/https), so for example `localhost`.|
- |`cookie.secure`|Should be `true` for SSL connections, and `false` for normal http connections.|
+ |`mode`|Should be either `development` or `production`. No more explanation needed.|
+ |`secret`|Whatever you want - used by express's session module.|
+ |`domain`|Should be the url where the site will be accessible from,usually `http://localhost` for non-Docker.|
+ |`serverDomain`|Should be the url where the backend will be accessible from, usually `http://localhost:8080` for non-Docker.|
+ |`serverPort`|Should be the port where the backend will listen on, should always be `8080` for Docker, and is recommended for non-Docker.|
+ |`isDocker`|Self-explanatory. Are you using Docker?|
+ |`serverPort`|Should be the port where the backend will listen on, should always be `8080` for Docker, and is recommended for non-Docker.|
+ |`apis.youtube.key`|Can be obtained by setting up a [YouTube API Key](https://developers.google.com/youtube/v3/getting-started). You need to use the YouTube Data API v3, and create an API key.|
+ |`apis.recaptcha.secret`|Can be obtained by setting up a [ReCaptcha Site (v3)](https://www.google.com/recaptcha/admin).|
+ |`apis.github`|Can be obtained by setting up a [GitHub OAuth Application](https://github.com/settings/developers). You need to fill in some values to create the OAuth application. The homepage is the homepage of frontend. The authorization callback url is the backend url with `/auth/github/authorize/callback` added at the end. For example `http://localhost:8080/auth/github/authorize/callback`.|
+ |`apis.discord.token`|Token for the Discord bot.|
+ |`apis.discord.loggingServer`|Server ID of the Discord logging server.|
+ |`apis.discord.loggingChannel`|ID of the channel to be used in the Discord logging server.|
+ |`apis.mailgun`|Can be obtained by setting up a [Mailgun account](http://www.mailgun.com/), or you can disable it.|
+ |`apis.spotify`|Can be obtained by setting up a [Spotify client id](https://developer.spotify.com/dashboard/applications), or you can disable it.|
+ |`apis.discogs`|Can be obtained by setting up a [Discogs application](https://www.discogs.com/settings/developers), or you can disable it.|
+ |`redis.url`|Should be left alone for Docker, and changed to `redis://localhost:6379/0` for non-Docker.|
+ |`redis.password`|Should be the Redis password you either put in your `startRedis.cmd` file for Windows, or `.env` for docker.|
+ |`mongo.url`|Needs to have the proper password for the MongoDB musare user, and for non-Docker you need to replace `@musare:27017` with `@localhost:27017`.|
+ |`cookie.domain`|Should be the ip or address you use to access the site, without protocols (http/https), so for example `localhost`.|
+ |`cookie.secure`|Should be `true` for SSL connections, and `false` for normal http connections.|
- |`serverDomain`|Should be the url where the backend will be accessible from, usually `http://localhost:8080` for non-Docker.|
- |`frontendDomain`|Should be the url where the frontend will be accessible from, usually `http://localhost` for docker or `http://localhost:80` for non-Docker.|
- |`frontendPort`|Should be the port where the frontend will be accessible from, should always be port `81` for Docker, and is recommended to be port `80` for non-Docker.|
- |`recaptcha.key`|Can be obtained by setting up a [ReCaptcha Site (v3)](https://www.google.com/recaptcha/admin).|
- |`cookie.domain`|Should be the ip or address you use to access the site, without protocols (http/https), so for example `localhost`.|
- |`cookie.secure`|Should be `true` for SSL connections, and `false` for normal http connections.|
- |`siteSettings.logo`|Path to the logo image, by default it is `/assets/wordmark.png`.|
- |`siteSettings.siteName`|Should be the name of the site.|
- |`siteSettings.socialLinks`|`github`, `twitter` and `facebook` are set to the official Musare accounts by default, but can be changed.|
+ |`serverDomain`|Should be the url where the backend will be accessible from, usually `http://localhost:8080` for non-Docker.|
+ |`frontendDomain`|Should be the url where the frontend will be accessible from, usually `http://localhost` for docker or `http://localhost:80` for non-Docker.|
+ |`frontendPort`|Should be the port where the frontend will be accessible from, should always be port `81` for Docker, and is recommended to be port `80` for non-Docker.|
+ |`recaptcha.key`|Can be obtained by setting up a [ReCaptcha Site (v3)](https://www.google.com/recaptcha/admin).|
+ |`cookie.domain`|Should be the ip or address you use to access the site, without protocols (http/https), so for example `localhost`.|
+ |`cookie.secure`|Should be `true` for SSL connections, and `false` for normal http connections.|
+ |`siteSettings.logo`|Path to the logo image, by default it is `/assets/wordmark.png`.|
+ |`siteSettings.siteName`|Should be the name of the site.|
+ |`siteSettings.socialLinks`|`github`, `twitter` and `facebook` are set to the official Musare accounts by default, but can be changed.|
5. Simply `cp .env.example .env` to setup your environment variables.
6. To setup [snyk](https://snyk.io/) (which is what we use for our precommit git-hooks), you will need to:
- - Setup an account
- - Go to [settings](https://app.snyk.io/account)
- - Copy the API token and set it as your `SNYK_TOKEN` environment variable.
+ - Setup an account
+ - Go to [settings](https://app.snyk.io/account)
+ - Copy the API token and set it as your `SNYK_TOKEN` environment variable.
We use snyk to test our dependencies / dev-dependencies for vulnerabilities.
@@ -110,51 +108,51 @@ We use snyk to test our dependencies / dev-dependencies for vulnerabilities.
#### Configuration
To configure docker configure the `.env` file to match your settings in `backend/config/default.json`.
-The configurable ports will be how you access the services on your machine, or what ports you will need to specify in your nginx files when using proxy_pass.
+The configurable ports will be how you access the services on your machine, or what ports you will need to specify in your nginx files when using proxy_pass.
`COMPOSE_PROJECT_NAME` should be a unique name for this installation, especially if you have multiple instances of Musare on the same machine.
`FRONTEND_MODE` should be either `dev` or `prod` (self-explanatory).
1. Build the backend and frontend Docker images (from the main folder)
- `docker-compose build`
+ `docker-compose build`
2. Set up the MongoDB database
- 1. Set the password for the admin/root user.
+ 1. Set the password for the admin/root user.
- In `.env` set the environment variable of `MONGO_ROOT_PASSWORD`.
+ In `.env` set the environment variable of `MONGO_ROOT_PASSWORD`.
- 2. Set the password for the musare user (the one the backend will use).
+ 2. Set the password for the musare user (the one the backend will use).
- In `.env` set the environment variable of `MONGO_USER_USERNAME` and `MONGO_USER_PASSWORD`.
+ In `.env` set the environment variable of `MONGO_USER_USERNAME` and `MONGO_USER_PASSWORD`.
- 3. Start the database (in detached mode), which will generate the correct MongoDB users.
+ 3. Start the database (in detached mode), which will generate the correct MongoDB users.
- `docker-compose up -d mongo`
+ `docker-compose up -d mongo`
3. Start redis and the mongo client in the background, as we usually don't need to monitor these for errors
- `docker-compose up -d mongoclient redis`
+ `docker-compose up -d mongoclient redis`
4. Start the backend and frontend in the foreground, so we can watch for errors during development
- `docker-compose up backend frontend`
+ `docker-compose up backend frontend`
5. You should now be able to begin development! The backend is auto reloaded when
you make changes and the frontend is auto compiled and live reloaded by webpack
when you make changes. You should be able to access Musare in your local browser
at `http://<docker-machine-ip>:8080/` where `<docker-machine-ip>` can be found below:
- - Docker for Windows / Mac: This is just `localhost`
+ - Docker for Windows / Mac: This is just `localhost`
- - Docker ToolBox: The output of `docker-machine ip default`
+ - Docker ToolBox: The output of `docker-machine ip default`
-If you are using linting extensions in IDEs/want to run `yarn lint`, you need to install the following locally (outside of Docker):
+If you are using linting extensions in IDEs/want to run `npm run lint`, you need to install the following locally (outside of Docker):
-```bash
- yarn global add eslint
- yarn add eslint-config-airbnb-base
-```
+```bash
+npm install -g eslint
+npm install -g eslint-config-airbnb-base
+```
### Standard Installation
@@ -164,9 +162,9 @@ Steps 1-4 are things you only have to do once. The steps to start servers follow
2. Create a file called `startMongo.cmd` in the main folder with the contents:
- In `startMongo.cmd` add `--auth` at the end of the first line
+ In `startMongo.cmd` add `--auth` at the end of the first line
4. In the folder where you installed Redis, edit the `redis.windows.conf` file. In there, look for the property `notify-keyspace-events`. Make sure that property is uncommented and has the value `Ex`. It should look like `notify-keyspace-events Ex` when done.
5. Create a file called `startRedis.cmd` in the main folder with the contents:
+// if (this.modulesLeft.length === 0) this.allModulesInitialized();
+// }
+
+// allModulesInitialized() {
+// this.logger.success("MODULE_MANAGER", "All modules have started!");
+// this.modules["discord"].sendAdminAlertMessage("The backend server started successfully.", "#00AA00", "Startup", false, []);
+// }
+
+// aModuleFailed(failedModule) {
+// this.logger.error("MODULE_MANAGER", `A module has failed, locking down. Module: ${failedModule.name}`);
+// this.modules["discord"].sendAdminAlertMessage(`The backend server failed to start due to a failing module: ${failedModule.name}.`, "#AA0000", "Startup", false, []);
- if (this.modulesLeft.length === 0) this.allModulesInitialized();
- }
-
- allModulesInitialized() {
- this.logger.success("MODULE_MANAGER", "All modules have started!");
- this.modules["discord"].sendAdminAlertMessage("The backend server started successfully.", "#00AA00", "Startup", false, []);
- }
-
- aModuleFailed(failedModule) {
- this.logger.error("MODULE_MANAGER", `A module has failed, locking down. Module: ${failedModule.name}`);
- this.modules["discord"].sendAdminAlertMessage(`The backend server failed to start due to a failing module: ${failedModule.name}.`, "#AA0000", "Startup", false, []);
+ `Importing a YouTube playlist to private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+ );
+ return cb({ status: "failure", message: err });
+ } else {
+ activities.runJob("ADD_ACTIVITY", {
+ userId: session.userId,
+ activityType: "added_songs_to_playlist",
+ payload: addedSongs,
+ });
+ console.log(
+ "SUCCESS",
+ "PLAYLIST_IMPORT",
+ `Successfully imported a YouTube playlist to private playlist "${playlistId}" for user "${session.userId}". Videos in playlist: ${videosInPlaylistTotal}, songs in playlist: ${songsInPlaylistTotal}, songs successfully added: ${songsSuccess}, songs failed: ${songsFail}.`
+ );
+ cb({
+ status: "success",
+ message: "Playlist has been successfully imported.",
+ data: playlist.songs,
+ stats: {
+ videosInPlaylistTotal,
+ songsInPlaylistTotal,
+ songsAddedSuccessfully: songsSuccess,
+ songsFailedToAdd: songsFail,
+ },
+ });
+ }
+ }
+ );
+ }
+ ),
+
+ /**
+ * Removes a song from a private playlist
+ *
+ * @param {Object} session - the session object automatically added by socket.io
+ * @param {String} songId - the id of the song we are removing from the private playlist
+ * @param {String} playlistId - the id of the playlist we are removing the song from
+ * @param {Function} cb - gets called with the result
- logger.error("PLAYLIST_REMOVE_SONG", `Removing song "${songId}" from private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
- return cb({ status: 'failure', message: err});
- } else {
- logger.success("PLAYLIST_REMOVE_SONG", `Successfully removed song "${songId}" from private playlist "${playlistId}" for user "${session.userId}".`);
- logger.error("PLAYLIST_UPDATE_DISPLAY_NAME", `Updating display name to "${displayName}" for private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
- return cb({ status: 'failure', message: err});
- }
- logger.success("PLAYLIST_UPDATE_DISPLAY_NAME", `Successfully updated display name to "${displayName}" for private playlist "${playlistId}" for user "${session.userId}".`);
- logger.error("PLAYLIST_MOVE_SONG_TO_TOP", `Moving song "${songId}" to the top for private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
- return cb({ status: 'failure', message: err});
- }
- logger.success("PLAYLIST_MOVE_SONG_TO_TOP", `Successfully moved song "${songId}" to the top for private playlist "${playlistId}" for user "${session.userId}".`);
- logger.error("PLAYLIST_MOVE_SONG_TO_BOTTOM", `Moving song "${songId}" to the bottom for private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
- return cb({ status: 'failure', message: err});
- }
- logger.success("PLAYLIST_MOVE_SONG_TO_BOTTOM", `Successfully moved song "${songId}" to the bottom for private playlist "${playlistId}" for user "${session.userId}".`);
- logger.success("STATIONS_UPDATE_BLACKLISTED_GENRES", `Updated station "${stationId}" blacklisted genres to "${newBlacklistedGenres}" successfully.`);
- return cb({'status': 'success', 'message': 'Successfully updated the blacklisted genres.'});
- });
- }),
-
- /**
- * Updates a station's party mode
- *
- * @param session
- * @param stationId - the station id
- * @param newPartyMode - the new station party mode
- if (totalSongs > 3) return next('The max amount of songs per user is 3, and only 2 in a row is allowed.');
- if (queue[queue.length - 2].requestedBy !== userId || queue[queue.length - 3] !== userId) return next('The max amount of songs per user is 3, and only 2 in a row is allowed.');
- if (!user.services.password || !user.services.password.password) return next('User does not have a password set, and probably uses GitHub to log in.');
- // TODO: Check every 30s/60s, for all sockets, if they are still allowed to be in the rooms they are in, and on socket at all (permission changing/banning)
+ // TODO: Check every 30s/60s, for all sockets, if they are still allowed to be in the rooms they are in, and on socket at all (permission changing/banning)
- To verify your email, please visit <a href="${config.get('serverDomain')}/auth/verify_email?code=${code}">${config.get('serverDomain')}/auth/verify_email?code=${code}</a>.
+ To verify your email, please visit <a href="${config.get(
- this.logger.stationIssue(`SKIP_STATION_CB - Station ID: ${stationId}.`);
-
- if (typeof cb !== 'function') cb = ()=>{};
-
- async.waterfall([
- (next) => {
- this.getStation(stationId, next, true);
- },
- (station, next) => {
- if (!station) return next('Station not found.');
- if (station.type === 'community' && station.partyMode && station.queue.length === 0) return next(null, null, -11, station); // Community station with party mode enabled and no songs in the queue
- if (station.type === 'community' && station.partyMode && station.queue.length > 0) { // Community station with party mode enabled and songs in the queue
- this.logger.info("TASK_SESSION_CLEAR", `Removing session ${sessionId} for user ${session.userId} since inactive for 30 days and not currently in use.`);
<meta name='description' content='On Musare you can listen to lots of different songs, playing 24/7 in our official stations and in user-made community stations!'>