浏览代码

Merge branch 'refactor-vite' into staging

Owen Diffey 2 年之前
父节点
当前提交
aa05edc044

+ 1 - 2
.gitignore

@@ -27,8 +27,7 @@ backend/build
 frontend/bundle-stats.json
 frontend/bundle-report.html
 frontend/node_modules/
-frontend/dist/build/
-frontend/dist/index.html
+frontend/build/
 frontend/dist/config/default.json
 
 npm

+ 117 - 111
.wiki/Configuration.md

@@ -1,140 +1,146 @@
 # Configuration
 
 ## Backend
+
 Location: `backend/config/default.json`
 
-| Property | Description |
-| --- | --- |
-| `mode` | Should be either `development` or `production`. |
-| `migration` | Should be set to `true` if you need to update MongoDB documents to a newer version after an update. Should be false at all other times. |
-| `secret` | Set to something unique and secure - 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/backend` for docker or `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. |
-| `registrationDisabled` | If set to true, users can't register accounts. |
-| `sendDataRequestEmails` | If `true` all admin users will be sent an email if a data request is received. |
-| `apis.youtube.key` | YouTube Data API v3 key, obtained from [here](https://developers.google.com/youtube/v3/getting-started). |
-| `apis.youtube.rateLimit` | Minimum interval between YouTube API requests in milliseconds. |
-| `apis.youtube.requestTimeout` | YouTube API requests timeout in milliseconds. |
-| `apis.youtube.retryAmount` | The amount of retries to perform of a failed YouTube API request. |
-| `apis.youtube.quotas` | Array of YouTube API quotas. |
-| `apis.youtube.quotas.type` | YouTube API quota type, should be one of `QUERIES_PER_DAY`, `QUERIES_PER_MINUTE` or `QUERIES_PER_100_SECONDS`. |
-| `apis.youtube.quotas.title` | YouTube API quota title. |
-| `apis.youtube.quotas.limit` | YouTube API quota limit. |
-| `apis.recaptcha.secret` | ReCaptcha Site v3 secret, obtained from [here](https://www.google.com/recaptcha/admin). |
-| `apis.recaptcha.enabled` | Whether to enable ReCaptcha at email registration. |
-| `apis.github.client` | GitHub OAuth Application client, obtained from [here](https://github.com/settings/developers). |
-| `apis.github.secret` | GitHub OAuth Application secret, obtained with client. |
-| `apis.github.redirect_uri` | The authorization callback url is the backend url with `/auth/github/authorize/callback` appended, for example `http://localhost/backend/auth/github/authorize/callback`. |
-| `apis.discogs.client` | Discogs Application client, obtained from [here](https://www.discogs.com/settings/developers). |
-| `apis.discogs.secret` | Discogs Application secret, obtained with client. |
-| `apis.discogs.enabled` | Whether to enable Discogs API usage. |
-| `cors.origin` | Array of allowed request origin urls, for example `http://localhost`. |
-| `smtp.host` | SMTP Host |
-| `smtp.port` | SMTP Port |
-| `smtp.auth.user` | SMTP Username |
-| `smtp.auth.pass` | SMTP Password |
-| `smtp.secure` | Whether SMTP is secured. |
-| `smtp.enabled` | Whether SMTP and sending emails is enabled. |
-| `mail.from` | The from field for mails sent from backend. |
-| `redis.url` | Should be left as default for Docker installations, else changed to `redis://localhost:6379/0`. |
-| `redis.password` | Redis password. |
-| `mongo.url` | For Docker replace temporary MongoDB musare user password with one specified in `.env`, and for non-Docker replace `@musare:27017` with `@localhost:27017`. |
-| `cookie.domain` | 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. |
-| `cookie.SIDname` | Name of the cookie stored for sessions. |
-| `blacklistedCommunityStationNames ` | Array of blacklisted community station names. |
-| `featuredPlaylists ` | Array of featured playlist id's. Playlist privacy must be public. |
-| `skipConfigVersionCheck` | Skips checking if the config version is outdated or not. Should almost always be set to false. |
-| `skipDbDocumentsVersionCheck` | Skips checking if there are any DB documents outdated or not. Should almost always be set to false. |
-| `debug.stationIssue` | If set to `true` it will enable the `/debug_station` API endpoint on the backend, which provides information useful to debugging stations not skipping, as well as capure all jobs specified in `debug.captureJobs`. 
-| `debug.traceUnhandledPromises` | Enables the trace-unhandled package, which provides detailed information when a promise is unhandled. |
-| `debug.captureJobs` | Array of jobs to capture for `debug.stationIssue`. |
-| `defaultLogging.hideType` | Filters out specified message types from log, for example `INFO`, `SUCCESS`, `ERROR` and `STATION_ISSUE`. |
-| `defaultLogging.blacklistedTerms` | Filters out messages containing specified terms from log, for example `success`. |
-| `customLoggingPerModule.[module].hideType` | Where `[module]` is a module name specify hideType as you would `defaultLogging.hideType` to overwrite default. |
-| `customLoggingPerModule.[module].blacklistedTerms` | Where `[module]` is a module name specify blacklistedTerms as you would `defaultLogging.blacklistedTerms` to overwrite default. |
-| `configVersion` | Version of the config. Every time the template changes, you should change your config accordingly and update the configVersion. |
+| Property                                           | Description                                                                                                                                                                                                          |
+| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `mode`                                             | Should be either `development` or `production`.                                                                                                                                                                      |
+| `migration`                                        | Should be set to `true` if you need to update MongoDB documents to a newer version after an update. Should be false at all other times.                                                                              |
+| `secret`                                           | Set to something unique and secure - 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/backend` for docker or `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.                                                                                          |
+| `registrationDisabled`                             | If set to true, users can't register accounts.                                                                                                                                                                       |
+| `sendDataRequestEmails`                            | If `true` all admin users will be sent an email if a data request is received.                                                                                                                                       |
+| `apis.youtube.key`                                 | YouTube Data API v3 key, obtained from [here](https://developers.google.com/youtube/v3/getting-started).                                                                                                             |
+| `apis.youtube.rateLimit`                           | Minimum interval between YouTube API requests in milliseconds.                                                                                                                                                       |
+| `apis.youtube.requestTimeout`                      | YouTube API requests timeout in milliseconds.                                                                                                                                                                        |
+| `apis.youtube.retryAmount`                         | The amount of retries to perform of a failed YouTube API request.                                                                                                                                                    |
+| `apis.youtube.quotas`                              | Array of YouTube API quotas.                                                                                                                                                                                         |
+| `apis.youtube.quotas.type`                         | YouTube API quota type, should be one of `QUERIES_PER_DAY`, `QUERIES_PER_MINUTE` or `QUERIES_PER_100_SECONDS`.                                                                                                       |
+| `apis.youtube.quotas.title`                        | YouTube API quota title.                                                                                                                                                                                             |
+| `apis.youtube.quotas.limit`                        | YouTube API quota limit.                                                                                                                                                                                             |
+| `apis.recaptcha.secret`                            | ReCaptcha Site v3 secret, obtained from [here](https://www.google.com/recaptcha/admin).                                                                                                                              |
+| `apis.recaptcha.enabled`                           | Whether to enable ReCaptcha at email registration.                                                                                                                                                                   |
+| `apis.github.client`                               | GitHub OAuth Application client, obtained from [here](https://github.com/settings/developers).                                                                                                                       |
+| `apis.github.secret`                               | GitHub OAuth Application secret, obtained with client.                                                                                                                                                               |
+| `apis.github.redirect_uri`                         | The authorization callback url is the backend url with `/auth/github/authorize/callback` appended, for example `http://localhost/backend/auth/github/authorize/callback`.                                            |
+| `apis.discogs.client`                              | Discogs Application client, obtained from [here](https://www.discogs.com/settings/developers).                                                                                                                       |
+| `apis.discogs.secret`                              | Discogs Application secret, obtained with client.                                                                                                                                                                    |
+| `apis.discogs.enabled`                             | Whether to enable Discogs API usage.                                                                                                                                                                                 |
+| `cors.origin`                                      | Array of allowed request origin urls, for example `http://localhost`.                                                                                                                                                |
+| `smtp.host`                                        | SMTP Host                                                                                                                                                                                                            |
+| `smtp.port`                                        | SMTP Port                                                                                                                                                                                                            |
+| `smtp.auth.user`                                   | SMTP Username                                                                                                                                                                                                        |
+| `smtp.auth.pass`                                   | SMTP Password                                                                                                                                                                                                        |
+| `smtp.secure`                                      | Whether SMTP is secured.                                                                                                                                                                                             |
+| `smtp.enabled`                                     | Whether SMTP and sending emails is enabled.                                                                                                                                                                          |
+| `mail.from`                                        | The from field for mails sent from backend.                                                                                                                                                                          |
+| `redis.url`                                        | Should be left as default for Docker installations, else changed to `redis://localhost:6379/0`.                                                                                                                      |
+| `redis.password`                                   | Redis password.                                                                                                                                                                                                      |
+| `mongo.url`                                        | For Docker replace temporary MongoDB musare user password with one specified in `.env`, and for non-Docker replace `@musare:27017` with `@localhost:27017`.                                                          |
+| `cookie.domain`                                    | 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.                                                                                                                                       |
+| `cookie.SIDname`                                   | Name of the cookie stored for sessions.                                                                                                                                                                              |
+| `blacklistedCommunityStationNames `                | Array of blacklisted community station names.                                                                                                                                                                        |
+| `featuredPlaylists `                               | Array of featured playlist id's. Playlist privacy must be public.                                                                                                                                                    |
+| `skipConfigVersionCheck`                           | Skips checking if the config version is outdated or not. Should almost always be set to false.                                                                                                                       |
+| `skipDbDocumentsVersionCheck`                      | Skips checking if there are any DB documents outdated or not. Should almost always be set to false.                                                                                                                  |
+| `debug.stationIssue`                               | If set to `true` it will enable the `/debug_station` API endpoint on the backend, which provides information useful to debugging stations not skipping, as well as capure all jobs specified in `debug.captureJobs`. |
+| `debug.traceUnhandledPromises`                     | Enables the trace-unhandled package, which provides detailed information when a promise is unhandled.                                                                                                                |
+| `debug.captureJobs`                                | Array of jobs to capture for `debug.stationIssue`.                                                                                                                                                                   |
+| `defaultLogging.hideType`                          | Filters out specified message types from log, for example `INFO`, `SUCCESS`, `ERROR` and `STATION_ISSUE`.                                                                                                            |
+| `defaultLogging.blacklistedTerms`                  | Filters out messages containing specified terms from log, for example `success`.                                                                                                                                     |
+| `customLoggingPerModule.[module].hideType`         | Where `[module]` is a module name specify hideType as you would `defaultLogging.hideType` to overwrite default.                                                                                                      |
+| `customLoggingPerModule.[module].blacklistedTerms` | Where `[module]` is a module name specify blacklistedTerms as you would `defaultLogging.blacklistedTerms` to overwrite default.                                                                                      |
+| `configVersion`                                    | Version of the config. Every time the template changes, you should change your config accordingly and update the configVersion.                                                                                      |
 
 ## Frontend
+
 Location: `frontend/dist/config/default.json`
 
-| Property | Description |
-| --- | --- |
-| `mode` | Should be either `development` or `production`. |
-| `backend.apiDomain` | Should be the url where the backend will be accessible from, usually `http://localhost/backend` for docker or `http://localhost:8080` for non-Docker. |
-| `backend.websocketsDomain` | Should be the same as the `apiDomain`, except using the `ws://` protocol instead of `http://` and with `/ws` at the end. |
-| `devServer.webSocketURL` | Should be the webpack-dev-server websocket URL, usually `ws://localhost/ws`. |
-| `devServer.port` | Should be the port where webpack-dev-server will be accessible from, should always be port `81` for Docker since nginx listens on port 80, and is recommended to be port `80` 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. |
-| `recaptcha.key` | ReCaptcha Site v3 key, obtained from [here](https://www.google.com/recaptcha/admin). |
-| `recaptcha.enabled` | Whether to enable ReCaptcha at email registration. |
-| `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. |
-| `cookie.SIDname` | Name of the cookie stored for sessions. |
-| `siteSettings.logo_white` | Path to the white logo image, by default it is `/assets/white_wordmark.png`. |
-| `siteSettings.logo_blue` | Path to the blue logo image, by default it is `/assets/blue_wordmark.png`. |
-| `siteSettings.logo_small` | Path to the small white logo image, by default it is `/assets/favicon/mstile-144x144.png`. |
-| `siteSettings.sitename` | Should be the name of the site. |
-| `siteSettings.footerLinks` | Add custom links to footer by specifying `"title": "url"`, e.g. `"GitHub": "https://github.com/Musare/Musare"`. You can disable about, team and news links (but not the pages themselves) by setting them to false, e.g. `"about": false`. |
-| `siteSettings.mediasession` | Whether to enable mediasession functionality. |
-| `siteSettings.christmas` | Whether to enable christmas theming. |
-| `siteSettings.registrationDisabled` | If set to true, users can't register accounts. |
-| `messages.accountRemoval` | Message to return to users on account removal. |
-| `shortcutOverrides` | Overwrite keyboard shortcuts, for example `"editSong.useAllDiscogs": { "keyCode": 68, "ctrl": true, "alt": true, "shift": false, "preventDefault": true }`. |
-| `debug.git.remote` | Allow the website/users to view the current Git repository's remote. [^1] |
-| `debug.git.remoteUrl` | Allow the website/users to view the current Git repository's remote URL. [^1] |
-| `debug.git.branch` | Allow the website/users to view the current Git repository's branch. [^1] |
-| `debug.git.latestCommit` | Allow the website/users to view the current Git repository's latest commit hash. [^1] |
-| `debug.git.latestCommitShort` | Allow the website/users to view the current Git repository's latest commit hash (short). [^1] |
-| `debug.version` | Allow the website/users to view the current package.json version. [^1] |
-| `skipConfigVersionCheck` | Skips checking if the config version is outdated or not. Should almost always be set to false. |
-| `configVersion` | Version of the config. Every time the template changes, you should change your config accordingly and update the configVersion. |
+| Property                            | Description                                                                                                                                                                                                                                |
+| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `mode`                              | Should be either `development` or `production`.                                                                                                                                                                                            |
+| `backend.apiDomain`                 | Should be the url where the backend will be accessible from, usually `http://localhost/backend` for docker or `http://localhost:8080` for non-Docker.                                                                                      |
+| `backend.websocketsDomain`          | Should be the same as the `apiDomain`, except using the `ws://` protocol instead of `http://` and with `/ws` at the end.                                                                                                                   |
+| `devServer.hmrClientPort`           | Should be the port on which the frontend will be accessible from, usually port `80`, or `443` if using SSL. Only used when running in dev mode.                                                                                            |
+| `devServer.port`                    | Should be the port where Vite's dev server will be accessible from, should always be port `81` for Docker since nginx listens on port 80, and is recommended to be port `80` for non-Docker. Only used when running in dev mode.           |
+| `frontendDomain`                    | Should be the url where the frontend will be accessible from, usually `http://localhost` for docker or `http://localhost:80` for non-Docker.                                                                                               |
+| `recaptcha.key`                     | ReCaptcha Site v3 key, obtained from [here](https://www.google.com/recaptcha/admin).                                                                                                                                                       |
+| `recaptcha.enabled`                 | Whether to enable ReCaptcha at email registration.                                                                                                                                                                                         |
+| `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.                                                                                                                                                             |
+| `cookie.SIDname`                    | Name of the cookie stored for sessions.                                                                                                                                                                                                    |
+| `siteSettings.logo_white`           | Path to the white logo image, by default it is `/assets/white_wordmark.png`.                                                                                                                                                               |
+| `siteSettings.logo_blue`            | Path to the blue logo image, by default it is `/assets/blue_wordmark.png`.                                                                                                                                                                 |
+| `siteSettings.logo_small`           | Path to the small white logo image, by default it is `/assets/favicon/mstile-144x144.png`.                                                                                                                                                 |
+| `siteSettings.sitename`             | Should be the name of the site.                                                                                                                                                                                                            |
+| `siteSettings.footerLinks`          | Add custom links to footer by specifying `"title": "url"`, e.g. `"GitHub": "https://github.com/Musare/Musare"`. You can disable about, team and news links (but not the pages themselves) by setting them to false, e.g. `"about": false`. |
+| `siteSettings.mediasession`         | Whether to enable mediasession functionality.                                                                                                                                                                                              |
+| `siteSettings.christmas`            | Whether to enable christmas theming.                                                                                                                                                                                                       |
+| `siteSettings.registrationDisabled` | If set to true, users can't register accounts.                                                                                                                                                                                             |
+| `messages.accountRemoval`           | Message to return to users on account removal.                                                                                                                                                                                             |
+| `shortcutOverrides`                 | Overwrite keyboard shortcuts, for example `"editSong.useAllDiscogs": { "keyCode": 68, "ctrl": true, "alt": true, "shift": false, "preventDefault": true }`.                                                                                |
+| `debug.git.remote`                  | Allow the website/users to view the current Git repository's remote. [^1]                                                                                                                                                                  |
+| `debug.git.remoteUrl`               | Allow the website/users to view the current Git repository's remote URL. [^1]                                                                                                                                                              |
+| `debug.git.branch`                  | Allow the website/users to view the current Git repository's branch. [^1]                                                                                                                                                                  |
+| `debug.git.latestCommit`            | Allow the website/users to view the current Git repository's latest commit hash. [^1]                                                                                                                                                      |
+| `debug.git.latestCommitShort`       | Allow the website/users to view the current Git repository's latest commit hash (short). [^1]                                                                                                                                              |
+| `debug.version`                     | Allow the website/users to view the current package.json version. [^1]                                                                                                                                                                     |
+| `skipConfigVersionCheck`            | Skips checking if the config version is outdated or not. Should almost always be set to false.                                                                                                                                             |
+| `configVersion`                     | Version of the config. Every time the template changes, you should change your config accordingly and update the configVersion.                                                                                                            |
 
 [^1]: Requires a frontend restart to update. The data will be available from the frontend console and by the frontend code.
 
 ## Docker Environment
+
 Location: `.env`
 
 In the table below the container host refers to the IP address that the docker container listens on, setting this to `127.0.0.1` for example will only expose the configured port to localhost, whereas setting to `0.0.0.0` will expose the port on all interfaces.
 
 The container port refers to the external docker container port, used to access services within the container. Changing this does not require any changes to configuration within container. For example setting the `MONGO_PORT` to `21018` will allow you to access the mongo service through that port, even though the application within the container is listening on `21017`.
 
-| Property | Description |
-| --- | --- |
-| `COMPOSE_PROJECT_NAME` | Should be a unique name for this installation, especially if you have multiple instances of Musare on the same machine. |
-| `RESTART_POLICY` | Restart policy for docker containers, values can be found [here](https://docs.docker.com/config/containers/start-containers-automatically/). |
-| `CONTAINER_MODE` | Should be either `prod` or `dev`.  |
-| `DOCKER_COMMAND` | Should be either `docker` or `podman`.  |
-| `BACKEND_HOST` | Backend container host. |
-| `BACKEND_PORT` | Backend container port. |
-| `FRONTEND_HOST` | Frontend container host. |
-| `FRONTEND_PORT` | Frontend container port. |
-| `FRONTEND_MODE` | Should be either `prod` or `dev`. |
-| `MONGO_HOST` | Mongo container host. |
-| `MONGO_PORT` | Mongo container port. |
-| `MONGO_ROOT_PASSWORD` | Password of the root/admin user for MongoDB. |
-| `MONGO_USER_USERNAME` | Application username for MongoDB. |
-| `MONGO_USER_PASSWORD` | Application password for MongoDB. |
-| `MONGO_DATA_LOCATION` | The location where MongoDB stores its data. Usually the `.db` folder inside the `Musare` folder. |
-| `MONGO_VERSION` | The MongoDB version to use for scripts and docker-compose. Must be numerical. Currently supported MongoDB versions are 4.0, 4.2, 4.4 and 5.0. |
-| `REDIS_HOST` | Redis container host. |
-| `REDIS_PORT` | Redis container port. |
-| `REDIS_PASSWORD` | Redis password. |
-| `REDIS_DATA_LOCATION` | The location where Redis stores its data. Usually the `.redis` folder inside the `Musare` folder. |
-| `BACKUP_LOCATION` | Directory to store musare.sh backups. Defaults to `/backups` in script location. |
-| `BACKUP_NAME` | Name of musare.sh backup files. Defaults to `musare-$(date +"%Y-%m-%d-%s").dump`. |
+| Property               | Description                                                                                                                                   |
+| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
+| `COMPOSE_PROJECT_NAME` | Should be a unique name for this installation, especially if you have multiple instances of Musare on the same machine.                       |
+| `RESTART_POLICY`       | Restart policy for docker containers, values can be found [here](https://docs.docker.com/config/containers/start-containers-automatically/).  |
+| `CONTAINER_MODE`       | Should be either `prod` or `dev`.                                                                                                             |
+| `DOCKER_COMMAND`       | Should be either `docker` or `podman`.                                                                                                        |
+| `BACKEND_HOST`         | Backend container host.                                                                                                                       |
+| `BACKEND_PORT`         | Backend container port.                                                                                                                       |
+| `FRONTEND_HOST`        | Frontend container host.                                                                                                                      |
+| `FRONTEND_PORT`        | Frontend container port.                                                                                                                      |
+| `FRONTEND_MODE`        | Should be either `prod` or `dev`.                                                                                                             |
+| `MONGO_HOST`           | Mongo container host.                                                                                                                         |
+| `MONGO_PORT`           | Mongo container port.                                                                                                                         |
+| `MONGO_ROOT_PASSWORD`  | Password of the root/admin user for MongoDB.                                                                                                  |
+| `MONGO_USER_USERNAME`  | Application username for MongoDB.                                                                                                             |
+| `MONGO_USER_PASSWORD`  | Application password for MongoDB.                                                                                                             |
+| `MONGO_DATA_LOCATION`  | The location where MongoDB stores its data. Usually the `.db` folder inside the `Musare` folder.                                              |
+| `MONGO_VERSION`        | The MongoDB version to use for scripts and docker-compose. Must be numerical. Currently supported MongoDB versions are 4.0, 4.2, 4.4 and 5.0. |
+| `REDIS_HOST`           | Redis container host.                                                                                                                         |
+| `REDIS_PORT`           | Redis container port.                                                                                                                         |
+| `REDIS_PASSWORD`       | Redis password.                                                                                                                               |
+| `REDIS_DATA_LOCATION`  | The location where Redis stores its data. Usually the `.redis` folder inside the `Musare` folder.                                             |
+| `BACKUP_LOCATION`      | Directory to store musare.sh backups. Defaults to `/backups` in script location.                                                              |
+| `BACKUP_NAME`          | Name of musare.sh backup files. Defaults to `musare-$(date +"%Y-%m-%d-%s").dump`.                                                             |
 
 ## Docker-compose override
-You may want to override the docker-compose files in some specific cases. For this, you can create a `docker-compose.override.yml` file.  
+
+You may want to override the docker-compose files in some specific cases. For this, you can create a `docker-compose.override.yml` file.
+
 ### Run backend on its own domain
+
 One example usecase for the override is to expose the backend port so you can run it separately from the frontend. An example file for this is as follows:
 
 ```yml
 services:
-  backend:
-    ports:
-      - "${BACKEND_HOST}:${BACKEND_PORT}:8080"
+    backend:
+        ports:
+            - "${BACKEND_HOST}:${BACKEND_PORT}:8080"
 ```
 
 This assumes that you have also set `BACKEND_PORT` inside your `.env` file.

+ 38 - 18
.wiki/Installation.md

@@ -1,4 +1,5 @@
 # Installation
+
 Musare can be installed with Docker (recommended) or without, guides for both installations can be found below.
 
 To update an existing installation please see [Upgrading](./Upgrading.md).
@@ -6,11 +7,13 @@ To update an existing installation please see [Upgrading](./Upgrading.md).
 ## Docker
 
 ### Dependencies
-- [Git](https://github.com/git-guides/install-git)
-- [Docker](https://docs.docker.com/get-docker/)
-- [docker-compose](https://docs.docker.com/compose/install/)
+
+-   [Git](https://github.com/git-guides/install-git)
+-   [Docker](https://docs.docker.com/get-docker/)
+-   [docker-compose](https://docs.docker.com/compose/install/)
 
 ### Instructions
+
 1. `git clone https://github.com/Musare/Musare.git`
 2. `cd Musare`
 3. `cp backend/config/template.json backend/config/default.json` and configure as per [Configuration](./Configuration.md#Backend)
@@ -35,23 +38,25 @@ Run this command in your shell. You will have to do this command for every shell
 ## Non-Docker
 
 ### Dependencies
-- [Git](https://github.com/git-guides/install-git)
-- [Redis](http://redis.io/download)
-- [MongoDB](https://www.mongodb.com/try/download/community)
-- [NodeJS](https://nodejs.org/en/download/)
-    - [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
-    - [nodemon](https://github.com/remy/nodemon#installation)
-    - [node-gyp](https://github.com/nodejs/node-gyp#installation)
-    - [webpack](https://webpack.js.org/guides/installation/#global-installation)
+
+-   [Git](https://github.com/git-guides/install-git)
+-   [Redis](http://redis.io/download)
+-   [MongoDB](https://www.mongodb.com/try/download/community)
+-   [NodeJS](https://nodejs.org/en/download/)
+    -   [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
+    -   [nodemon](https://github.com/remy/nodemon#installation)
 
 ### Instructions
+
 1. `git clone https://github.com/Musare/Musare.git`
 2. `cd Musare`
 3. [Setup MongoDB](#Setting-up-MongoDB)
 4. [Setup Redis](#Setting-up-Redis)
 5. `cp backend/config/template.json backend/config/default.json` and configure as per [Configuration](./Configuration.md#Backend)
 6. `cp frontend/dist/config/template.json frontend/dist/config/default.json` and configure as per [Configuration](./Configuration.md#Frontend)
-7. Start services
+7. `cd frontend && npm install && cd ..`
+8. `cd backend && npm install && cd ..`
+9. Start services
     - **Linux**
         1. Execute `systemctl start redis mongod`
         2. Execute `cd frontend && npm run dev` and `cd backend && npm run dev` separately.
@@ -60,7 +65,7 @@ Run this command in your shell. You will have to do this command for every shell
         - **Manual**
             1. Run `startRedis.cmd` and `startMongo.cmd` to start Redis and Mongo.
             2. Execute `cd frontend && npm run dev` and `cd backend && npm run dev` separately.
-8. **(optional)** Register a new user on the website and grant the admin role by running the following in the mongodb shell.
+10. **(optional)** Register a new user on the website and grant the admin role by running the following in the mongodb shell.
     ```bash
     use musare
     db.auth("MUSAREDBUSER","MUSAREDBPASSWORD")
@@ -68,33 +73,43 @@ Run this command in your shell. You will have to do this command for every shell
     ```
 
 ### Setting up MongoDB
-- **Windows Only**
+
+-   **Windows Only**
+
     1. In the root directory, create a folder called `.database`
     2. Create a file called `startMongo.cmd` in the root directory with the contents:
 
         `"C:\Program Files\MongoDB\Server\4.0\bin\mongod.exe" --dbpath "C:\Path\To\Musare\.database"`
 
         Make sure to adjust your paths accordingly.
+
     3. Start the database by executing the script `startMongo.cmd` you just made
-- Set up the MongoDB database itself
+
+-   Set up the MongoDB database itself
+
     1. Start MongoDB
         - **Linux** Execute `systemctl start mongod`
         - **Windows** Execute the `startMongo.cmd` script you just made
     2. Connect to Mongo from a command prompt
 
         `mongo admin`
+
     3. Create an admin user
 
         `db.createUser({user: 'admin', pwd: 'PASSWORD_HERE', roles: [{role: 'userAdminAnyDatabase', db: 'admin'}]})`
+
     4. Connect to the Musare database
 
         `use musare`
+
     5. Create the "musare" user
 
         `db.createUser({user: 'musare', pwd: 'OTHER_PASSWORD_HERE', roles: [{role: 'readWrite', db: 'musare'}]})`
+
     6. Exit
 
         `exit`
+
     7. Add the authentication
         - **Linux**
             1. Add `auth=true` to `/etc/mongod.conf`
@@ -104,18 +119,23 @@ Run this command in your shell. You will have to do this command for every shell
             2. Restart MongoDB
 
 ### Setting up Redis
-- **Windows**
+
+-   **Windows**
+
     1. In the folder where you installed Redis, edit the `redis.windows.conf` file
+
         1. In there, look for the property `notify-keyspace-events`.
         2. Make sure that property is uncommented and has the value `Ex`.
-            
+
             It should look like `notify-keyspace-events Ex` when done.
+
     2. Create a file called `startRedis.cmd` in the main folder with the contents:
 
         `"C:\Path\To\Redis\redis-server.exe" "C:\Path\To\Redis\redis.windows.conf" "--requirepass" "PASSWORD"`
 
         And again, make sure that the paths lead to the proper config and executable. Replace `PASSWORD` with your Redis password.
-- **Linux**
+
+-   **Linux**
     1. In `/etc/redis/redis.conf`
         1. Uncomment `notify-keyspace-events` and set its value to `Ex`.
         2. Uncomment `requirepass foobared` and replace foobared with your Redis password.

+ 2 - 1
docker-compose.yml

@@ -23,14 +23,15 @@ services:
       args:
         FRONTEND_MODE: "${FRONTEND_MODE:-prod}"
     restart: ${RESTART_POLICY:-unless-stopped}
+    user: root
     ports:
       - "${FRONTEND_HOST:-0.0.0.0}:${FRONTEND_PORT:-80}:80"
     volumes:
       - ./.git:/opt/app/.parent_git:ro
-      - /opt/app/node_modules
       - ./frontend/dist/config:/opt/app/dist/config
     environment:
       - FRONTEND_MODE=${FRONTEND_MODE:-prod}
+      - CONTAINER_MODE=${CONTAINER_MODE:-prod}
     links:
       - backend
 

+ 0 - 9
frontend/.babelrc

@@ -1,9 +0,0 @@
-{
-	"presets": [ ["@babel/preset-env", { "modules": false }] ],
-	"plugins": [
-		"@babel/plugin-transform-runtime",
-		"@babel/plugin-syntax-dynamic-import",
-		"@babel/plugin-proposal-object-rest-spread"
-	],
-	"comments": false
-}

+ 2 - 1
frontend/.dockerignore

@@ -1,3 +1,4 @@
 node_modules/
 Dockerfile
-dist/config/default.json
+config/default.json
+build/

+ 3 - 2
frontend/.eslintrc

@@ -6,11 +6,12 @@
 		"node": true,
 		"es6": true
 	},
+	"parser": "vue-eslint-parser",
 	"parserOptions": {
 		"ecmaVersion": 2018,
 		"sourceType": "module",
-		"parser": "@babel/eslint-parser",
-		"requireConfigFile": false
+		"requireConfigFile": false,
+		"parser": "@babel/eslint-parser"
 	},
 	"extends": [
 		"airbnb-base",

+ 1 - 3
frontend/Dockerfile

@@ -3,12 +3,11 @@ FROM node:16.15 AS musare_frontend
 ARG FRONTEND_MODE=prod
 ENV FRONTEND_MODE=${FRONTEND_MODE}
 ENV SUPPRESS_NO_CONFIG_WARNING=1
+ENV NODE_CONFIG_DIR=./dist/config
 
 RUN apt-get update
 RUN apt-get install nginx -y
 
-RUN npm install -g webpack@5.73.0 webpack-cli@4.9.2
-
 RUN mkdir -p /opt/app
 WORKDIR /opt/app
 
@@ -28,4 +27,3 @@ RUN chmod u+x entrypoint.sh
 ENTRYPOINT bash /opt/app/entrypoint.sh
 
 EXPOSE 80/tcp
-EXPOSE 80/udp

+ 3 - 3
frontend/dist/config/template.json

@@ -8,8 +8,8 @@
 		"websocketsDomain": "ws://localhost/backend/ws"
 	},
 	"devServer": {
-		"port": "81",
-		"webSocketURL": "ws://localhost/ws"
+		"port": 81,
+		"hmrClientPort": 80
 	},
 	"frontendDomain": "http://localhost",
 	"mode": "development",
@@ -53,5 +53,5 @@
 		"version": true
 	},
 	"skipConfigVersionCheck": false,
-	"configVersion": 11
+	"configVersion": 12
 }

+ 0 - 56
frontend/dist/index.tpl.html

@@ -1,56 +0,0 @@
-<!DOCTYPE html>
-<html lang='en'>
-
-<head>
-	<title><%= htmlWebpackPlugin.options.title %></title>
-
-	<meta charset='UTF-8'>
-	<meta http-equiv='X-UA-Compatible' content='IE=edge'>
-	<meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no'>
-	<meta name='keywords' content='music, <%= htmlWebpackPlugin.options.title %>, musare, songs, song catalogue, listen, station, station, radio, open source'>
-	<meta name='description' content='<%= htmlWebpackPlugin.options.title %> is an open-source collaborative music listening and catalogue curation application. Currently supporting YouTube based content.'>
-	<meta name='copyright' content='© Copyright Musare 2015-2022 All Right Reserved'>
-
-	<link rel='apple-touch-icon' sizes='57x57' href='/assets/favicon/apple-touch-icon-57x57.png?v=06042016'>
-	<link rel='apple-touch-icon' sizes='60x60' href='/assets/favicon/apple-touch-icon-60x60.png?v=06042016'>
-	<link rel='apple-touch-icon' sizes='72x72' href='/assets/favicon/apple-touch-icon-72x72.png?v=06042016'>
-	<link rel='apple-touch-icon' sizes='76x76' href='/assets/favicon/apple-touch-icon-76x76.png?v=06042016'>
-	<link rel='apple-touch-icon' sizes='114x114' href='/assets/favicon/apple-touch-icon-114x114.png?v=06042016'>
-	<link rel='apple-touch-icon' sizes='120x120' href='/assets/favicon/apple-touch-icon-120x120.png?v=06042016'>
-	<link rel='apple-touch-icon' sizes='144x144' href='/assets/favicon/apple-touch-icon-144x144.png?v=06042016'>
-	<link rel='apple-touch-icon' sizes='152x152' href='/assets/favicon/apple-touch-icon-152x152.png?v=06042016'>
-	<link rel='apple-touch-icon' sizes='180x180' href='/assets/favicon/apple-touch-icon-180x180.png?v=06042016'>
-	<link rel='icon' type='image/png' href='/assets/favicon/favicon-32x32.png?v=06042016' sizes='32x32'>
-	<link rel='icon' type='image/png' href='/assets/favicon/favicon-194x194.png?v=06042016' sizes='194x194'>
-	<link rel='icon' type='image/png' href='/assets/favicon/favicon-96x96.png?v=06042016' sizes='96x96'>
-	<link rel='icon' type='image/png' href='/assets/favicon/android-chrome-192x192.png?v=06042016' sizes='192x192'>
-	<link rel='icon' type='image/png' href='/assets/favicon/favicon-16x16.png?v=06042016' sizes='16x16'>
-	<link rel='manifest' href='/assets/favicon/manifest.json?v=06042016'>
-	<link rel='mask-icon' href='/assets/favicon/safari-pinned-tab.svg?v=06042016' color='#03a9f4'>
-	<link rel='shortcut icon' href='/assets/favicon/favicon.ico?v=06042016'>
-	<meta name='msapplication-TileColor' content='#03a9f4'>
-	<meta name='msapplication-TileImage' content='/assets/favicon/mstile-144x144.png?v=06042016'>
-	<meta name='theme-color' content='#03a9f4'>
-	<meta name='google' content='nositelinkssearchbox' />
-
-	<script src='https://www.youtube.com/iframe_api'></script>
-
-	<!--Musare version: <%= htmlWebpackPlugin.options.debug.version %>-->
-	<script>
-		const MUSARE_VERSION = "<%= htmlWebpackPlugin.options.debug.version %>";
-		const MUSARE_GIT_REMOTE = "<%= htmlWebpackPlugin.options.debug.git.remote %>";
-		const MUSARE_GIT_REMOTE_URL = "<%= htmlWebpackPlugin.options.debug.git.remoteUrl %>";
-		const MUSARE_GIT_BRANCH = "<%= htmlWebpackPlugin.options.debug.git.branch %>";
-		const MUSARE_GIT_LATEST_COMMIT = "<%= htmlWebpackPlugin.options.debug.git.latestCommit %>";
-		const MUSARE_GIT_LATEST_COMMIT_SHORT = "<%= htmlWebpackPlugin.options.debug.git.latestCommitShort %>";
-	</script>
-</head>
-
-<body>
-	<div id="root"></div>
-	<div id="toasts-container" class="position-right position-bottom">
-		<div id="toasts-content"></div>
-	</div>
-</body>
-
-</html>

文件差异内容过多而无法显示
+ 0 - 0
frontend/dist/vendor/lofig.1.3.4.min.js


+ 8 - 1
frontend/entrypoint.sh

@@ -1,6 +1,13 @@
 #!/bin/bash
 
-if [[ "${FRONTEND_MODE}" = "prod" ]]; then
+if [[ "${CONTAINER_MODE}" == "dev" ]]; then
+    npm install
+fi
+
+if [[ "${FRONTEND_MODE}" == "prod" ]]; then
+    if [[ "${CONTAINER_MODE}" == "dev" ]]; then
+        npm run prod
+    fi
     nginx -c /opt/app/prod.nginx.conf -g "daemon off;"
 elif [ "${FRONTEND_MODE}" == "dev" ]; then
     nginx -c /opt/app/dev.nginx.conf

文件差异内容过多而无法显示
+ 1915 - 2259
frontend/package-lock.json


+ 10 - 24
frontend/package.json

@@ -13,48 +13,37 @@
   "repository": "https://github.com/Musare/Musare",
   "scripts": {
     "lint": "npx eslint src --ext .js,.vue",
-    "dev": "npx webpack serve --config webpack.dev.js",
-    "prod": "npx webpack --config webpack.prod.js"
+    "dev": "npx vite",
+    "prod": "npx vite build --emptyOutDir"
   },
   "devDependencies": {
-    "@babel/core": "^7.18.2",
+    "@babel/core": "^7.18.5",
     "@babel/eslint-parser": "^7.18.2",
-    "@babel/plugin-proposal-object-rest-spread": "^7.18.0",
-    "@babel/plugin-syntax-dynamic-import": "^7.8.3",
-    "@babel/plugin-transform-runtime": "^7.18.2",
-    "@babel/preset-env": "^7.18.2",
     "@vue/compiler-sfc": "^3.2.36",
-    "babel-loader": "^8.2.5",
-    "css-loader": "^6.7.1",
     "eslint": "^8.17.0",
     "eslint-config-prettier": "^8.5.0",
     "eslint-plugin-import": "^2.26.0",
     "eslint-plugin-prettier": "^4.0.0",
-    "eslint-plugin-vue": "^9.1.0",
-    "eslint-webpack-plugin": "^3.1.1",
+    "eslint-plugin-vue": "^9.1.1",
     "fetch": "^1.1.0",
     "less": "^4.1.2",
-    "less-loader": "^11.0.0",
     "prettier": "^2.6.2",
-    "style-loader": "^3.3.1",
-    "style-resources-loader": "^1.5.0",
-    "vue-style-loader": "^4.1.3",
-    "webpack-cli": "^4.9.2",
-    "webpack-dev-server": "^4.9.1"
+    "vite-plugin-dynamic-import": "^0.9.9",
+    "vue-eslint-parser": "^9.0.2"
   },
   "dependencies": {
-    "@babel/runtime": "^7.18.3",
+    "@vitejs/plugin-vue": "^2.3.3",
     "can-autoplay": "^3.0.2",
     "chart.js": "^3.8.0",
     "config": "^3.3.7",
     "date-fns": "^2.28.0",
     "dompurify": "^2.3.8",
     "eslint-config-airbnb-base": "^15.0.0",
-    "html-webpack-plugin": "^5.5.0",
     "lofig": "^1.3.4",
     "marked": "^4.0.16",
     "normalize.css": "^8.0.1",
     "toasters": "^2.3.1",
+    "vite": "^2.9.12",
     "vue": "^3.2.36",
     "vue-chartjs": "^4.1.1",
     "vue-content-loader": "^2.0.1",
@@ -63,9 +52,6 @@
     "vue-router": "^4.0.15",
     "vue-tippy": "^6.0.0-alpha.57",
     "vuedraggable": "^4.1.0",
-    "vuex": "^4.0.2",
-    "webpack": "^5.73.0",
-    "webpack-bundle-analyzer": "^4.5.0",
-    "webpack-merge": "^5.8.0"
+    "vuex": "^4.0.2"
   }
-}
+}

+ 8 - 8
frontend/prod.nginx.conf

@@ -5,25 +5,25 @@ events {
 }
 
 http {
-    include       /etc/nginx/mime.types;
-    default_type  application/octet-stream;
+    include /etc/nginx/mime.types;
+    default_type application/octet-stream;
 
-    sendfile        off;
+    sendfile off;
 
-    keepalive_timeout  65;
+    keepalive_timeout 65;
 
     server {
-        listen       80;
+        listen 80;
         server_name _;
 
-        root /opt/app/dist;
+        root /opt/app/build;
 
         location / {
-            try_files $uri /build/$uri /build/index.html =404;
+            try_files $uri /$uri /index.html =404;
         }
 
         location /backend {
-            proxy_set_header X-Real-IP  $remote_addr;
+            proxy_set_header X-Real-IP $remote_addr;
             proxy_set_header X-Forwarded-For $remote_addr;
             proxy_set_header Host $host;
 

+ 1 - 1
frontend/src/components/ModalManager.vue

@@ -16,7 +16,7 @@ import { mapModalComponents } from "@/vuex_helpers";
 
 export default {
 	computed: {
-		...mapModalComponents("./components/modals", {
+		...mapModalComponents("components/modals", {
 			editUser: "EditUser.vue",
 			login: "Login.vue",
 			register: "Register.vue",

+ 1 - 1
frontend/src/components/PlaylistItem.vue

@@ -37,7 +37,7 @@
 </template>
 
 <script>
-import utils from "../../js/utils";
+import utils from "@/utils";
 
 export default {
 	props: {

+ 1 - 1
frontend/src/components/SongItem.vue

@@ -171,7 +171,7 @@ import { mapActions, mapState } from "vuex";
 import { formatDistance, parseISO } from "date-fns";
 
 import AddToPlaylistDropdown from "./AddToPlaylistDropdown.vue";
-import utils from "../../js/utils";
+import utils from "@/utils";
 
 export default {
 	components: { AddToPlaylistDropdown },

+ 2 - 2
frontend/src/components/modals/EditNews.vue

@@ -77,7 +77,7 @@
 <script>
 import { mapActions, mapGetters } from "vuex";
 import { marked } from "marked";
-import { sanitize } from "dompurify";
+import DOMPurify from "dompurify";
 import Toast from "toasters";
 import { formatDistance } from "date-fns";
 
@@ -152,7 +152,7 @@ export default {
 			}
 		},
 		marked,
-		sanitize,
+		sanitize: DOMPurify.sanitize,
 		getTitle() {
 			let title = "";
 			const preview = document.getElementById("preview");

+ 1 - 1
frontend/src/components/modals/EditPlaylist/index.vue

@@ -259,7 +259,7 @@ import Settings from "./Tabs/Settings.vue";
 import AddSongs from "./Tabs/AddSongs.vue";
 import ImportPlaylists from "./Tabs/ImportPlaylists.vue";
 
-import utils from "../../../../js/utils";
+import utils from "@/utils";
 
 export default {
 	components: {

+ 1 - 1
frontend/src/components/modals/EditSongs.vue

@@ -194,7 +194,7 @@ import editSong from "@/store/modules/modals/editSong";
 export default {
 	components: {
 		EditSong: defineAsyncComponent(() =>
-			import("@/components/modals/EditSong")
+			import("@/components/modals/EditSong/index.vue")
 		),
 		SongItem
 	},

+ 2 - 2
frontend/src/components/modals/WhatIsNew.vue

@@ -26,7 +26,7 @@
 <script>
 import { formatDistance } from "date-fns";
 import { marked } from "marked";
-import { sanitize } from "dompurify";
+import DOMPurify from "dompurify";
 import { mapActions } from "vuex";
 
 import { mapModalState } from "@/vuex_helpers";
@@ -58,7 +58,7 @@ export default {
 	},
 	methods: {
 		marked,
-		sanitize,
+		sanitize: DOMPurify.sanitize,
 		formatDistance,
 		...mapActions("modalVisibility", ["openModal"])
 	}

+ 138 - 0
frontend/src/index.html

@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>{{ title }}</title>
+
+		<meta charset="UTF-8" />
+		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
+		<meta
+			name="viewport"
+			content="width=device-width, initial-scale=1, user-scalable=no"
+		/>
+		<meta
+			name="keywords"
+			content="music, {{ title }}, musare, songs, song catalogue, listen, station, station, radio, open source"
+		/>
+		<meta
+			name="description"
+			content="{{ title }} is an open-source collaborative music listening and catalogue curation application. Currently supporting YouTube based content."
+		/>
+		<meta
+			name="copyright"
+			content="© Copyright Musare 2015-2022 All Right Reserved"
+		/>
+
+		<link
+			rel="apple-touch-icon"
+			sizes="57x57"
+			href="/assets/favicon/apple-touch-icon-57x57.png?v=06042016"
+		/>
+		<link
+			rel="apple-touch-icon"
+			sizes="60x60"
+			href="/assets/favicon/apple-touch-icon-60x60.png?v=06042016"
+		/>
+		<link
+			rel="apple-touch-icon"
+			sizes="72x72"
+			href="/assets/favicon/apple-touch-icon-72x72.png?v=06042016"
+		/>
+		<link
+			rel="apple-touch-icon"
+			sizes="76x76"
+			href="/assets/favicon/apple-touch-icon-76x76.png?v=06042016"
+		/>
+		<link
+			rel="apple-touch-icon"
+			sizes="114x114"
+			href="/assets/favicon/apple-touch-icon-114x114.png?v=06042016"
+		/>
+		<link
+			rel="apple-touch-icon"
+			sizes="120x120"
+			href="/assets/favicon/apple-touch-icon-120x120.png?v=06042016"
+		/>
+		<link
+			rel="apple-touch-icon"
+			sizes="144x144"
+			href="/assets/favicon/apple-touch-icon-144x144.png?v=06042016"
+		/>
+		<link
+			rel="apple-touch-icon"
+			sizes="152x152"
+			href="/assets/favicon/apple-touch-icon-152x152.png?v=06042016"
+		/>
+		<link
+			rel="apple-touch-icon"
+			sizes="180x180"
+			href="/assets/favicon/apple-touch-icon-180x180.png?v=06042016"
+		/>
+		<link
+			rel="icon"
+			type="image/png"
+			href="/assets/favicon/favicon-32x32.png?v=06042016"
+			sizes="32x32"
+		/>
+		<link
+			rel="icon"
+			type="image/png"
+			href="/assets/favicon/favicon-194x194.png?v=06042016"
+			sizes="194x194"
+		/>
+		<link
+			rel="icon"
+			type="image/png"
+			href="/assets/favicon/favicon-96x96.png?v=06042016"
+			sizes="96x96"
+		/>
+		<link
+			rel="icon"
+			type="image/png"
+			href="/assets/favicon/android-chrome-192x192.png?v=06042016"
+			sizes="192x192"
+		/>
+		<link
+			rel="icon"
+			type="image/png"
+			href="/assets/favicon/favicon-16x16.png?v=06042016"
+			sizes="16x16"
+		/>
+		<link rel="manifest" href="/assets/favicon/manifest.json?v=06042016" />
+		<link
+			rel="mask-icon"
+			href="/assets/favicon/safari-pinned-tab.svg?v=06042016"
+			color="#03a9f4"
+		/>
+		<link
+			rel="shortcut icon"
+			href="/assets/favicon/favicon.ico?v=06042016"
+		/>
+		<meta name="msapplication-TileColor" content="#03a9f4" />
+		<meta
+			name="msapplication-TileImage"
+			content="/assets/favicon/mstile-144x144.png?v=06042016"
+		/>
+		<meta name="theme-color" content="#03a9f4" />
+		<meta name="google" content="nositelinkssearchbox" />
+
+		<script src="https://www.youtube.com/iframe_api"></script>
+
+		<!--Musare version: {{ version }}-->
+		<!--
+			Git info
+			Remote: {{ gitRemote }}
+			Remote URL: {{ gitRemoteUrl }}
+			Branch: {{ gitBranch }}
+			Latest commit: {{ gitLatestCommit }}
+			Latest commit short: {{ gitLatestCommitShort }}
+		-->
+	</head>
+
+	<body>
+		<div id="root"></div>
+		<div id="toasts-container" class="position-right position-bottom">
+			<div id="toasts-content"></div>
+		</div>
+		<script type="module" src="/main.js"></script>
+	</body>
+</html>

+ 15 - 12
frontend/src/main.js

@@ -11,9 +11,14 @@ import store from "./store";
 
 import AppComponent from "./App.vue";
 
-const REQUIRED_CONFIG_VERSION = 11;
+const defaultConfigURL = new URL(
+	"config/default.json",
+	import.meta.url
+).toString();
 
-lofig.folder = "../config/default.json";
+const REQUIRED_CONFIG_VERSION = 12;
+
+lofig.folder = defaultConfigURL;
 
 const handleMetadata = attrs => {
 	lofig.get("siteSettings.sitename").then(siteName => {
@@ -61,16 +66,14 @@ app.component("PageMetadata", {
 	}
 });
 
-const globalComponents = require.context(
-	"@/components/global/",
-	true,
-	/\.vue$/i
+const globalComponents = import.meta.glob("@/components/global/*.vue");
+Object.entries(globalComponents).forEach(
+	async ([componentFilePath, definition]) => {
+		const componentName = componentFilePath.split("/").pop().split(".")[0];
+		const component = await definition();
+		app.component(componentName, component.default);
+	}
 );
-globalComponents.keys().forEach(componentFilePath => {
-	const componentName = componentFilePath.split("/").pop().split(".")[0];
-	const component = globalComponents(componentFilePath);
-	app.component(componentName, component.default || component);
-});
 
 app.directive("scroll", {
 	mounted(el, binding) {
@@ -274,7 +277,7 @@ router.beforeEach((to, from, next) => {
 
 app.use(router);
 
-lofig.folder = "/config/default.json";
+lofig.folder = defaultConfigURL;
 
 (async () => {
 	lofig.fetchConfig().then(config => {

+ 1 - 1
frontend/src/pages/Admin/Playlists.vue

@@ -90,7 +90,7 @@ import { mapActions } from "vuex";
 import AdvancedTable from "@/components/AdvancedTable.vue";
 import RunJobDropdown from "@/components/RunJobDropdown.vue";
 
-import utils from "../../../js/utils";
+import utils from "@/utils";
 
 export default {
 	components: {

+ 2 - 2
frontend/src/pages/News.vue

@@ -39,7 +39,7 @@
 import { formatDistance } from "date-fns";
 import { mapGetters } from "vuex";
 import { marked } from "marked";
-import { sanitize } from "dompurify";
+import DOMPurify from "dompurify";
 
 import ws from "@/ws";
 
@@ -93,7 +93,7 @@ export default {
 	},
 	methods: {
 		marked,
-		sanitize,
+		sanitize: DOMPurify.sanitize,
 		formatDistance,
 		init() {
 			this.socket.dispatch("news.getPublished", res => {

+ 1 - 1
frontend/src/pages/Profile/index.vue

@@ -107,7 +107,7 @@ import ws from "@/ws";
 
 import TabQueryHandler from "@/mixins/TabQueryHandler.vue";
 
-import ProfilePicture from "@/components/ProfilePicture";
+import ProfilePicture from "@/components/ProfilePicture.vue";
 
 import RecentActivity from "./Tabs/RecentActivity.vue";
 import Playlists from "./Tabs/Playlists.vue";

+ 1 - 1
frontend/src/pages/Station/index.vue

@@ -709,7 +709,7 @@ import AddToPlaylistDropdown from "@/components/AddToPlaylistDropdown.vue";
 import SongItem from "@/components/SongItem.vue";
 import Z404 from "../404.vue";
 
-import utils from "../../../js/utils";
+import utils from "@/utils";
 
 import StationSidebar from "./Sidebar/index.vue";
 

+ 0 - 0
frontend/js/utils.js → frontend/src/utils.js


+ 3 - 1
frontend/src/vuex_helpers.js

@@ -66,7 +66,9 @@ const mapModalComponents = (baseDirectory, map) => {
 	const modalComponents = {};
 	Object.entries(map).forEach(([mapKey, mapValue]) => {
 		modalComponents[mapKey] = () =>
-			defineAsyncComponent(() => import(`${baseDirectory}/${mapValue}`));
+			defineAsyncComponent(() =>
+				import(`./${baseDirectory}/${mapValue}`)
+			);
 	});
 	return modalComponents;
 };

+ 175 - 0
frontend/vite.config.js

@@ -0,0 +1,175 @@
+import path from "path";
+import vue from "@vitejs/plugin-vue";
+import dynamicImport from "vite-plugin-dynamic-import";
+import config from "config";
+import fs from "fs";
+
+const fetchVersionAndGitInfo = () => {
+	const debug = {
+		git: {
+			remote: "",
+			remoteUrl: "",
+			branch: "",
+			latestCommit: "",
+			latestCommitShort: ""
+		},
+		version: ""
+	};
+
+	try {
+		const packageJson = JSON.parse(
+			fs.readFileSync("./package.json").toString()
+		);
+
+		console.log(`Musare version: ${packageJson.version}.`);
+		if (config.has("debug.version") && config.get("debug.version"))
+			debug.version = packageJson.version;
+	} catch (e) {
+		console.log(`Could not get package info: ${e.message}.`);
+	}
+
+	try {
+		let gitFolder = null;
+		if (fs.existsSync(".parent_git/HEAD")) gitFolder = ".parent_git";
+		else if (fs.existsSync("../.git/HEAD")) gitFolder = "../.git";
+
+		if (gitFolder) {
+			const headContents = fs
+				.readFileSync(`${gitFolder}/HEAD`)
+				.toString()
+				.replace(/\n/g, "");
+			const branch = /ref: refs\/heads\/([.A-Za-z0-9_-]+)/.exec(
+				headContents
+			)[1];
+
+			const configContents = fs
+				.readFileSync(`${gitFolder}/config`)
+				.toString()
+				.replace(/\t/g, "")
+				.split("\n");
+
+			let remote;
+			let remoteUrl;
+			let latestCommit;
+			let latestCommitShort;
+
+			if (configContents.indexOf(`[branch "${branch}"]`) >= 0) {
+				remote = /remote = (.+)/.exec(
+					configContents[
+						configContents.indexOf(`[branch "${branch}"]`) + 1
+					]
+				)[1];
+
+				remoteUrl = /url = (.+)/.exec(
+					configContents[
+						configContents.indexOf(`[remote "${remote}"]`) + 1
+					]
+				)[1];
+
+				latestCommit = fs
+					.readFileSync(`${gitFolder}/refs/heads/${branch}`)
+					.toString()
+					.replace(/\n/g, "");
+
+				latestCommitShort = latestCommit.substr(0, 7);
+			}
+
+			console.log(
+				`Git branch: ${remote}/${branch}. Remote url: ${remoteUrl}. Latest commit: ${latestCommit} (${latestCommitShort}).`
+			);
+			if (config.get("debug.git.remote")) debug.git.remote = remote;
+			if (config.get("debug.git.remoteUrl"))
+				debug.git.remoteUrl = remoteUrl;
+			if (config.get("debug.git.branch")) debug.git.branch = branch;
+			if (config.get("debug.git.latestCommit"))
+				debug.git.latestCommit = latestCommit;
+			if (config.get("debug.git.latestCommitShort"))
+				debug.git.latestCommitShort = latestCommitShort;
+		}
+	} catch (e) {
+		console.log(`Could not get Git info: ${e.message}.`, e);
+	}
+
+	return debug;
+};
+
+const debug = fetchVersionAndGitInfo();
+
+const siteName = config.has("siteSettings.sitename")
+	? config.get("siteSettings.sitename")
+	: "Musare";
+
+const htmlPlugin = () => ({
+	name: "html-transform",
+	transformIndexHtml(originalHtml) {
+		let html = originalHtml;
+
+		html = html.replace(/{{ title }}/g, siteName);
+		html = html.replace(/{{ version }}/g, debug.version);
+		html = html.replace(/{{ gitRemote }}/g, debug.git.remote);
+		html = html.replace(/{{ gitRemoteUrl }}/g, debug.git.remoteUrl);
+		html = html.replace(/{{ gitBranch }}/g, debug.git.branch);
+		html = html.replace(/{{ gitLatestCommit }}/g, debug.git.latestCommit);
+		html = html.replace(
+			/{{ gitLatestCommitShort }}/g,
+			debug.git.latestCommitShort
+		);
+
+		return html;
+	}
+});
+
+const mode = process.env.FRONTEND_MODE || "dev";
+
+let server = null;
+
+if (mode === "dev")
+	server = {
+		host: "0.0.0.0",
+		port: config.has("devServer.port") ? config.get("devServer.port") : 81,
+		strictPort: true,
+		hmr: {
+			clientPort: config.has("devServer.hmrClientPort")
+				? config.get("devServer.hmrClientPort")
+				: 80
+		}
+	};
+
+export default {
+	mode: mode === "dev" ? "development" : "production",
+	root: "src",
+	publicDir: "../dist",
+	base: "/",
+	resolve: {
+		alias: [
+			{
+				find: "@",
+				replacement: path.resolve(__dirname, "src")
+			}
+		]
+	},
+	define: {
+		__VUE_OPTIONS_API__: true,
+		__VUE_PROD_DEVTOOLS__: false,
+		MUSARE_VERSION: JSON.stringify(debug.version),
+		MUSARE_GIT_REMOTE: JSON.stringify(debug.git.remote),
+		MUSARE_GIT_REMOTE_URL: JSON.stringify(debug.git.remoteUrl),
+		MUSARE_GIT_BRANCH: JSON.stringify(debug.git.branch),
+		MUSARE_GIT_LATEST_COMMIT: JSON.stringify(debug.git.latestCommit),
+		MUSARE_GIT_LATEST_COMMIT_SHORT: JSON.stringify(
+			debug.git.latestCommitShort
+		)
+	},
+	plugins: [vue(), htmlPlugin(), dynamicImport()],
+	css: {
+		preprocessorOptions: {
+			less: {
+				additionalData: `@import "@/styles/variables.less";`
+			}
+		}
+	},
+	server,
+	build: {
+		outDir: "../build"
+	}
+};

+ 0 - 183
frontend/webpack.common.js

@@ -1,183 +0,0 @@
-process.env.NODE_CONFIG_DIR = `${__dirname}/dist/config/`;
-const path = require("path");
-const fs = require("fs");
-const config = require("config");
-const { VueLoaderPlugin } = require("vue-loader");
-const HtmlWebpackPlugin = require("html-webpack-plugin");
-const ESLintPlugin = require("eslint-webpack-plugin");
-const { DefinePlugin } = require("webpack");
-
-const fetchVersionAndGitInfo = cb => {
-	const debug = {
-		git: {
-			remote: "",
-			remoteUrl: "",
-			branch: "",
-			latestCommit: "",
-			latestCommitShort: ""
-		},
-		version: ""
-	};
-
-	try {
-		const packageJson = JSON.parse(
-			fs.readFileSync("./package.json").toString()
-		);
-		const headContents = fs
-			.readFileSync(".parent_git/HEAD")
-			.toString()
-			.replace(/\n/g, "");
-		const branch = new RegExp("ref: refs/heads/([.A-Za-z0-9_-]+)").exec(
-			headContents
-		)[1];
-		const configContents = fs
-			.readFileSync(".parent_git/config")
-			.toString()
-			.replace(/\t/g, "")
-			.split("\n");
-		const remote = new RegExp("remote = (.+)").exec(
-			configContents[configContents.indexOf(`[branch "${branch}"]`) + 1]
-		)[1];
-		const remoteUrl = new RegExp("url = (.+)").exec(
-			configContents[configContents.indexOf(`[remote "${remote}"]`) + 1]
-		)[1];
-		const latestCommit = fs
-			.readFileSync(`.parent_git/refs/heads/${branch}`)
-			.toString()
-			.replace(/\n/g, "");
-		const latestCommitShort = latestCommit.substr(0, 7);
-
-		console.log(`Musare version: ${packageJson.version}.`);
-		console.log(
-			`Git branch: ${remote}/${branch}. Remote url: ${remoteUrl}. Latest commit: ${latestCommit} (${latestCommitShort}).`
-		);
-
-		if (config.get("debug.version")) debug.version = packageJson.version;
-		if (config.get("debug.git.remote")) debug.git.remote = remote;
-		if (config.get("debug.git.remoteUrl")) debug.git.remoteUrl = remoteUrl;
-		if (config.get("debug.git.branch")) debug.git.branch = branch;
-		if (config.get("debug.git.latestCommit"))
-			debug.git.latestCommit = latestCommit;
-		if (config.get("debug.git.latestCommitShort"))
-			debug.git.latestCommitShort = latestCommitShort;
-	} catch (e) {
-		console.log(`Could not get Git info: ${e.message}.`);
-	}
-
-	cb(debug);
-};
-
-fetchVersionAndGitInfo(() => {});
-
-class InsertDebugInfoPlugin {
-	apply(compiler) {
-		compiler.hooks.compilation.tap("InsertDebugInfoPlugin", compilation => {
-			HtmlWebpackPlugin.getHooks(
-				compilation
-			).beforeAssetTagGeneration.tapAsync(
-				"InsertDebugInfoPlugin",
-				(data, cb) => {
-					fetchVersionAndGitInfo(debug => {
-						data.plugin.userOptions.debug.version = debug.version;
-						data.plugin.userOptions.debug.git.remote =
-							debug.git.remote;
-						data.plugin.userOptions.debug.git.remoteUrl =
-							debug.git.remoteUrl;
-						data.plugin.userOptions.debug.git.branch =
-							debug.git.branch;
-						data.plugin.userOptions.debug.git.latestCommit =
-							debug.git.latestCommit;
-						data.plugin.userOptions.debug.git.latestCommitShort =
-							debug.git.latestCommitShort;
-						cb(null, data);
-					});
-				}
-			);
-		});
-	}
-}
-
-module.exports = {
-	entry: "./src/main.js",
-	output: {
-		path: `${__dirname}/dist/build/`,
-		filename: "[name].[contenthash].js"
-	},
-	resolve: {
-		alias: {
-			"@": path.resolve(__dirname, "./src/")
-		},
-		extensions: [".js", ".vue"]
-	},
-	plugins: [
-		new VueLoaderPlugin(),
-		new HtmlWebpackPlugin({
-			title: config.has("siteSettings.sitename")
-				? config.get("siteSettings.sitename")
-				: "Musare",
-			hash: true,
-			template: "dist/index.tpl.html",
-			inject: "body",
-			filename: "index.html",
-			debug: {
-				git: {
-					remote: "",
-					remoteUrl: "",
-					branch: "",
-					latestCommit: "",
-					latestCommitShort: ""
-				},
-				version: ""
-			}
-		}),
-		new ESLintPlugin(),
-		new InsertDebugInfoPlugin(),
-		new DefinePlugin({
-			__VUE_OPTIONS_API__: true,
-			__VUE_PROD_DEVTOOLS__: false
-		})
-	],
-	module: {
-		rules: [
-			{
-				test: /\.vue$/,
-				loader: "vue-loader",
-				exclude: /node_modules/
-			},
-			{
-				test: /\.js$/,
-				loader: "babel-loader",
-				exclude: /node_modules/
-			},
-			{
-				test: /\.css$/,
-				use: ["style-loader", "css-loader"]
-			},
-			{
-				test: /\.less$/i,
-				exclude: /node_modules/,
-				use: [
-					"vue-style-loader",
-					{
-						loader: "css-loader",
-						options: {
-							url: false
-						}
-					},
-					"less-loader",
-					{
-						loader: "style-resources-loader",
-						options: {
-							patterns: [
-								path.resolve(
-									__dirname,
-									"./src/styles/variables.less"
-								)
-							]
-						}
-					}
-				]
-			}
-		]
-	}
-};

+ 0 - 32
frontend/webpack.dev.js

@@ -1,32 +0,0 @@
-process.env.NODE_CONFIG_DIR = `${__dirname}/dist/config/`;
-const config = require("config");
-
-const { merge } = require("webpack-merge");
-const common = require("./webpack.common.js");
-module.exports = merge(common, {
-	mode: "development",
-	devtool: "inline-source-map",
-	output: {
-		publicPath: "/"
-	},
-	resolve: {
-		alias: {
-			styles: "src/styles",
-			vue: "vue/dist/vue.esm-bundler.js"
-		}
-	},
-	devServer: {
-		static: {
-			directory: "./dist/",
-			watch: true
-		},
-		client: {
-			webSocketURL: config.get("devServer.webSocketURL")
-		},
-		hot: true,
-		historyApiFallback: true,
-		port: config.get("devServer.port"),
-		host: "0.0.0.0",
-		allowedHosts: "all"
-	}
-});

+ 0 - 49
frontend/webpack.prod.js

@@ -1,49 +0,0 @@
-const { merge } = require("webpack-merge");
-const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
-
-const common = require("./webpack.common.js");
-
-module.exports = merge(common, {
-	mode: "production",
-	devtool: "source-map",
-	output: {
-		publicPath: "/build/"
-	},
-	resolve: {
-		alias: {
-			styles: "src/styles",
-			vue: "vue/dist/vue.esm-bundler.js"
-		}
-	},
-	plugins: [
-		new BundleAnalyzerPlugin({
-			analyzerMode: "static"
-		})
-	],
-	optimization: {
-		runtimeChunk: "single",
-		splitChunks: {
-			cacheGroups: {
-				commons: {
-					name: "vendors",
-					test: /[\\/]node_modules[\\/](vue|vuex|vue-router)[\\/]/,
-					chunks: "all"
-				},
-				ui: {
-					name: false,
-					test(module) {
-						return module.resource && (
-							module.resource.includes("Modal.vue") ||
-							module.resource.includes(
-								"AddToPlaylistDropdown.vue"
-							) ||
-							module.resource.includes("MainHeader.vue") ||
-							module.resource.includes("MainFooter.vue")
-						);
-					},
-					enforce: true
-				}
-			}
-		}
-	}
-});

部分文件因为文件数量过多而无法显示