├── .dockerignore ├── .env.example ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── actions │ ├── cache-front │ │ └── action.yml │ ├── cache-matcher │ │ └── action.yml │ ├── cache-scanner │ │ └── action.yml │ ├── cache-server │ │ └── action.yml │ └── dockerize │ │ └── action.yml ├── release.yml └── workflows │ ├── front.yml │ ├── matcher.yml │ ├── scanner.yml │ └── server.yml ├── .gitignore ├── LICENSE ├── README.md ├── assets └── examples │ ├── album-page.png │ ├── album-top.png │ ├── artist-page.png │ ├── live-albums.png │ ├── player.png │ ├── releases.png │ ├── song-groups.png │ ├── synced-lyrics.gif │ └── versions-page.png ├── biome.json ├── docker-compose.dev.yml ├── docker-compose.prod.yml ├── docker-compose.yml ├── front ├── .dockerignore ├── .gitignore ├── .yarn │ └── releases │ │ └── yarn-4.4.0.cjs ├── .yarnrc.yml ├── Dockerfile ├── Dockerfile.dev ├── apps │ └── web │ │ ├── next.config.js │ │ ├── package.json │ │ ├── public │ │ ├── src │ │ ├── components │ │ │ ├── admin-grid.tsx │ │ │ ├── authentication │ │ │ │ ├── form.tsx │ │ │ │ └── wall.tsx │ │ │ ├── error-page.tsx │ │ │ ├── external-metadata-badge.tsx │ │ │ ├── genre-button.tsx │ │ │ ├── head.tsx │ │ │ ├── highlight-card │ │ │ │ ├── index.tsx │ │ │ │ └── resource │ │ │ │ │ └── album.tsx │ │ │ ├── keyboard-bindings-modal.tsx │ │ │ ├── library-form.tsx │ │ │ ├── lyrics.tsx │ │ │ ├── modal-page.tsx │ │ │ ├── page-section.tsx │ │ │ ├── player │ │ │ │ ├── controls │ │ │ │ │ ├── common.tsx │ │ │ │ │ ├── expanded.tsx │ │ │ │ │ ├── lyrics.tsx │ │ │ │ │ ├── minimized.tsx │ │ │ │ │ └── slider.tsx │ │ │ │ └── index.tsx │ │ │ ├── relation-page-header │ │ │ │ ├── index.tsx │ │ │ │ └── resource │ │ │ │ │ ├── artist.tsx │ │ │ │ │ └── song.tsx │ │ │ ├── release-tracklist.tsx │ │ │ ├── resource-description.tsx │ │ │ ├── scaffold.tsx │ │ │ ├── section-header.tsx │ │ │ ├── settings │ │ │ │ ├── libraries.tsx │ │ │ │ ├── ui.tsx │ │ │ │ └── users.tsx │ │ │ ├── song-grid.tsx │ │ │ ├── tab-router.tsx │ │ │ ├── themed-image.tsx │ │ │ └── toaster.tsx │ │ ├── contexts │ │ │ └── keybindings.tsx │ │ ├── i18n.tsx │ │ ├── middleware.ts │ │ ├── pages │ │ │ ├── 404.tsx │ │ │ ├── 500.tsx │ │ │ ├── _app.tsx │ │ │ ├── _document.tsx │ │ │ ├── albums │ │ │ │ ├── compilations.tsx │ │ │ │ └── index.tsx │ │ │ ├── artists │ │ │ │ ├── [slugOrId] │ │ │ │ │ ├── albums.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── rare-songs.tsx │ │ │ │ │ ├── songs.tsx │ │ │ │ │ └── videos.tsx │ │ │ │ └── index.tsx │ │ │ ├── genres │ │ │ │ ├── [slugOrId].tsx │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ ├── labels │ │ │ │ └── [slugOrId].tsx │ │ │ ├── playlists │ │ │ │ ├── [slugOrId] │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── releases │ │ │ │ └── [slugOrId].tsx │ │ │ ├── search │ │ │ │ └── [[...query]].tsx │ │ │ ├── settings │ │ │ │ └── [[...panel]].tsx │ │ │ ├── songs │ │ │ │ ├── [slugOrId] │ │ │ │ │ └── [...tab].tsx │ │ │ │ └── index.tsx │ │ │ └── videos │ │ │ │ └── index.tsx │ │ ├── ssr.ts │ │ ├── theme │ │ │ ├── font.ts │ │ │ ├── provider.tsx │ │ │ ├── style.ts │ │ │ ├── styles.css │ │ │ └── theme.ts │ │ └── utils │ │ │ ├── accent-color.ts │ │ │ ├── getSlugOrId.ts │ │ │ └── themed-sx-value.ts │ │ └── tsconfig.json ├── assets │ ├── banner1-black.png │ ├── banner1-white.png │ ├── banner2-black.png │ ├── banner2-white.png │ ├── favicon-black.ico │ ├── favicon-white.ico │ ├── icon-black.png │ └── icon-white.png ├── biome.json ├── package.json ├── packages │ ├── api │ │ ├── package.json │ │ ├── src │ │ │ ├── hook.ts │ │ │ ├── index.ts │ │ │ ├── queries.ts │ │ │ └── query.ts │ │ └── tsconfig.json │ ├── components │ │ ├── package.json │ │ ├── src │ │ │ ├── _internal.ts │ │ │ ├── actions │ │ │ │ ├── auth.tsx │ │ │ │ ├── download.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── library-task.tsx │ │ │ │ ├── link.tsx │ │ │ │ ├── merge.tsx │ │ │ │ ├── playlist.tsx │ │ │ │ ├── refresh-metadata.tsx │ │ │ │ ├── resource-type.tsx │ │ │ │ ├── share.tsx │ │ │ │ ├── show-track-info.tsx │ │ │ │ └── update-illustration.tsx │ │ │ ├── artist-avatar.tsx │ │ │ ├── blurhash.tsx │ │ │ ├── contextual-menu │ │ │ │ ├── index.tsx │ │ │ │ └── resource │ │ │ │ │ ├── album.tsx │ │ │ │ │ ├── artist.tsx │ │ │ │ │ ├── playlist.tsx │ │ │ │ │ ├── release-track.tsx │ │ │ │ │ ├── release.tsx │ │ │ │ │ ├── song.tsx │ │ │ │ │ ├── track.tsx │ │ │ │ │ └── video.tsx │ │ │ ├── empty-state.tsx │ │ │ ├── fade.tsx │ │ │ ├── gradient-background.tsx │ │ │ ├── icons.tsx │ │ │ ├── illustration.tsx │ │ │ ├── infinite │ │ │ │ ├── controls │ │ │ │ │ ├── controls.tsx │ │ │ │ │ ├── filters │ │ │ │ │ │ ├── control.ts │ │ │ │ │ │ ├── library.ts │ │ │ │ │ │ └── resource-type.ts │ │ │ │ │ ├── layout.ts │ │ │ │ │ └── sort.ts │ │ │ │ ├── grid.tsx │ │ │ │ ├── list.tsx │ │ │ │ ├── resource │ │ │ │ │ ├── album.tsx │ │ │ │ │ ├── artist.tsx │ │ │ │ │ ├── playlist.tsx │ │ │ │ │ ├── song.tsx │ │ │ │ │ ├── track.tsx │ │ │ │ │ └── video.tsx │ │ │ │ ├── scroll.tsx │ │ │ │ └── view.tsx │ │ │ ├── list-item │ │ │ │ ├── index.tsx │ │ │ │ └── resource │ │ │ │ │ ├── album.tsx │ │ │ │ │ ├── artist.tsx │ │ │ │ │ ├── playlist.tsx │ │ │ │ │ ├── release.tsx │ │ │ │ │ ├── song.tsx │ │ │ │ │ ├── track.tsx │ │ │ │ │ └── video.tsx │ │ │ ├── song-type-icon.tsx │ │ │ ├── tile │ │ │ │ ├── index.tsx │ │ │ │ ├── resource │ │ │ │ │ ├── album.tsx │ │ │ │ │ ├── artist.tsx │ │ │ │ │ ├── genre.tsx │ │ │ │ │ ├── playlist.tsx │ │ │ │ │ ├── release.tsx │ │ │ │ │ └── video.tsx │ │ │ │ └── row.tsx │ │ │ └── track-file-info.tsx │ │ └── tsconfig.json │ ├── models │ │ ├── package.json │ │ ├── src │ │ │ ├── album.ts │ │ │ ├── artist.ts │ │ │ ├── disc.ts │ │ │ ├── exceptions.ts │ │ │ ├── external-metadata.ts │ │ │ ├── file.ts │ │ │ ├── genre.ts │ │ │ ├── illustration.ts │ │ │ ├── index.ts │ │ │ ├── label.ts │ │ │ ├── layout.ts │ │ │ ├── library.ts │ │ │ ├── lyrics.ts │ │ │ ├── pagination.ts │ │ │ ├── playlist.ts │ │ │ ├── release.ts │ │ │ ├── resource.ts │ │ │ ├── scrobblers.ts │ │ │ ├── search.ts │ │ │ ├── song-group.ts │ │ │ ├── song.ts │ │ │ ├── sorting.ts │ │ │ ├── task.ts │ │ │ ├── track.ts │ │ │ ├── tracklist.ts │ │ │ ├── user.ts │ │ │ ├── utils.ts │ │ │ └── video.ts │ │ └── tsconfig.json │ ├── state │ │ ├── package.json │ │ ├── src │ │ │ ├── player.ts │ │ │ ├── store.ts │ │ │ └── user.ts │ │ └── tsconfig.json │ ├── tsconfig.base.json │ └── utils │ │ ├── __tests__ │ │ └── utils │ │ │ └── format-duration.ts │ │ ├── package.json │ │ ├── src │ │ ├── blurhashToDataUrl.ts │ │ ├── constants.ts │ │ ├── copy-link.ts │ │ ├── date.ts │ │ ├── format-artists.ts │ │ ├── format-duration.ts │ │ ├── gen-list.ts │ │ ├── i18next.d.ts │ │ ├── is-ssr.ts │ │ ├── query-param.ts │ │ ├── random.ts │ │ └── uncapitalize.ts │ │ └── tsconfig.json ├── sonar-project.properties ├── translations │ ├── en.json │ ├── fr.json │ ├── index.ts │ └── ru.json ├── tsconfig.base.json └── yarn.lock ├── matcher ├── .gitignore ├── Dockerfile ├── README.md ├── assets │ ├── allmusic │ │ └── icon.png │ ├── discogs │ │ └── icon.png │ ├── genius │ │ └── icon.png │ ├── lrclib │ │ └── icon.png │ ├── metacritic │ │ └── icon.png │ ├── musicbrainz │ │ └── icon.png │ └── wikipedia │ │ └── icon.png ├── matcher │ ├── __main__.py │ ├── api.py │ ├── bootstrap.py │ ├── context.py │ ├── matcher │ │ ├── album.py │ │ ├── artist.py │ │ ├── common.py │ │ └── song.py │ ├── models │ │ ├── api │ │ │ ├── domain.py │ │ │ ├── dto.py │ │ │ ├── page.py │ │ │ └── provider.py │ │ ├── event.py │ │ └── match_result.py │ ├── providers │ │ ├── allmusic.py │ │ ├── base.py │ │ ├── boilerplate.py │ │ ├── discogs.py │ │ ├── domain.py │ │ ├── factory.py │ │ ├── features.py │ │ ├── genius.py │ │ ├── lrclib.py │ │ ├── metacritic.py │ │ ├── musicbrainz.py │ │ ├── wikidata.py │ │ └── wikipedia.py │ ├── settings.py │ └── utils.py ├── pyproject.toml ├── requirements.txt └── tests │ ├── __main__.py │ ├── api.py │ ├── assets │ ├── bad_type.json │ ├── missing_field.json │ └── settings.json │ ├── matcher │ ├── album.py │ ├── artist.py │ ├── common.py │ └── song.py │ ├── providers │ ├── allmusic.py │ ├── discogs.py │ ├── genius.py │ ├── lrclib.py │ ├── metacritic.py │ ├── musicbrainz.py │ └── wikipedia.py │ └── settings.py ├── nginx.conf.template ├── scanner ├── .gitignore ├── Dockerfile ├── Dockerfile.dev ├── README.md ├── app │ ├── context.go │ ├── main.go │ └── routes.go ├── go.mod ├── go.sum ├── internal │ ├── api │ │ ├── api.go │ │ └── models.go │ ├── checksum.go │ ├── config │ │ ├── config.go │ │ ├── user_settings.go │ │ └── user_settings_test.go │ ├── constants.go │ ├── filesystem │ │ └── filesystem.go │ ├── fingerprint.go │ ├── illustration │ │ ├── embedded.go │ │ ├── inline.go │ │ ├── inline_test.go │ │ └── thumbnail.go │ ├── lyrics.go │ ├── lyrics_test.go │ ├── metadata.go │ ├── metadata_test.go │ ├── parser │ │ ├── embedded.go │ │ ├── embedded_test.go │ │ ├── parser.go │ │ ├── parser_test.go │ │ ├── path.go │ │ └── path_test.go │ ├── tasks │ │ ├── clean.go │ │ ├── common.go │ │ ├── illustration.go │ │ ├── refresh.go │ │ ├── scan.go │ │ ├── task.go │ │ └── worker.go │ ├── utils.go │ └── validation.go ├── sonar-project.properties └── testdata │ ├── cover.jpg │ ├── dreams.m4a │ ├── lyrics.lrc │ ├── test.flac │ ├── test.opus │ └── user_settings │ ├── settings-empty-file.json │ ├── settings-empty-regex.json │ ├── settings-invalid.json │ ├── settings-missing-metadata-order.json │ ├── settings-missing-metadata-source.json │ ├── settings-missing-metadata.json │ ├── settings-missing-regex.json │ ├── settings-wrong-type-metadata-source.json │ ├── settings-wrong-type.json │ ├── settings.json │ └── settings2.json ├── server ├── .gitignore ├── .prettierrc ├── .yarn │ └── releases │ │ └── yarn-4.4.0.cjs ├── .yarnrc.yml ├── Dockerfile ├── Dockerfile.dev ├── README.md ├── biome.json ├── nest-cli.json ├── package.json ├── prisma │ ├── migrations │ │ ├── 20220930082500_init │ │ │ └── migration.sql │ │ ├── 20221026083004_new_album_types │ │ │ └── migration.sql │ │ ├── 20221106165421_user_table │ │ │ └── migration.sql │ │ ├── 20230107210819_album_master │ │ │ └── migration.sql │ │ ├── 20230108090513_song_master │ │ │ └── migration.sql │ │ ├── 20230113113317_lyrics_cascade_delete │ │ │ └── migration.sql │ │ ├── 20230207092742_registration_dates │ │ │ └── migration.sql │ │ ├── 20230321162528_providers │ │ │ └── migration.sql │ │ ├── 20230414174453_playlists │ │ │ └── migration.sql │ │ ├── 20230705144951_illustrations │ │ │ └── migration.sql │ │ ├── 20230727174917_song_types │ │ │ └── migration.sql │ │ ├── 20230914151519_acappela_song_type │ │ │ └── migration.sql │ │ ├── 20230918121029_release_external_ids │ │ │ └── migration.sql │ │ ├── 20231028165358_secondary_song_artists │ │ │ └── migration.sql │ │ ├── 20231104152731_external_ids_description │ │ │ └── migration.sql │ │ ├── 20231118192451_album_external_id_rating │ │ │ └── migration.sql │ │ ├── 20231202193154_album_genres │ │ │ └── migration.sql │ │ ├── 20231207184811_bonus_track_field │ │ │ └── migration.sql │ │ ├── 20231208190658_non_music_song_type │ │ │ └── migration.sql │ │ ├── 20231221132029_optional_bitrate_and_duration │ │ │ └── migration.sql │ │ ├── 20240103140210_song_groups │ │ │ └── migration.sql │ │ ├── 20240110124438_illustration_aspect_ratio │ │ │ └── migration.sql │ │ ├── 20240111124042_release_illustration │ │ │ └── migration.sql │ │ ├── 20240120094441_remastered_track_field │ │ │ └── migration.sql │ │ ├── 20240120104148_release_name_extensions │ │ │ └── migration.sql │ │ ├── 20240131095559_play_history │ │ │ └── migration.sql │ │ ├── 20240207162850_artist_image_url │ │ │ └── migration.sql │ │ ├── 20240603054248_new_illustration_table_artist │ │ │ └── migration.sql │ │ ├── 20240604052515_new_illustration_table_playlist │ │ │ └── migration.sql │ │ ├── 20240604155616_new_illustration_table_releases │ │ │ └── migration.sql │ │ ├── 20240609090034_unique_slugs │ │ │ └── migration.sql │ │ ├── 20240609094144_medley_song_type │ │ │ └── migration.sql │ │ ├── 20240610154553_illustrations_views │ │ │ └── migration.sql │ │ ├── 20240618172303_fix_track_illustration_view │ │ │ └── migration.sql │ │ ├── 20240906115347_rename_checksum_field │ │ │ └── migration.sql │ │ ├── 20240915085203_new_external_metadata │ │ │ └── migration.sql │ │ ├── 20241102151239_search_history │ │ │ └── migration.sql │ │ ├── 20241212090123_fix_acappella_spelling │ │ │ └── migration.sql │ │ ├── 20241220080246_add_acoustid │ │ │ └── migration.sql │ │ ├── 20241221080934_track_thumbnail │ │ │ └── migration.sql │ │ ├── 20241221104003_standalone_track_illustration │ │ │ └── migration.sql │ │ ├── 20241221112653_standalone_track │ │ │ └── migration.sql │ │ ├── 20250102092150_new_video_model │ │ │ └── migration.sql │ │ ├── 20250102092916_prevent_silent_deletion_of_non_empty_songs │ │ │ └── migration.sql │ │ ├── 20250102110631_video_add_registration_date │ │ │ └── migration.sql │ │ ├── 20250104154901_video_master_track │ │ │ └── migration.sql │ │ ├── 20250105130635_search_history_add_videos │ │ │ └── migration.sql │ │ ├── 20250117200829_fix_many_to_many_prisma_breaking │ │ │ └── migration.sql │ │ ├── 20250305132915_song_bpm │ │ │ └── migration.sql │ │ ├── 20250308201912_lyrics_rename_plain_column │ │ │ └── migration.sql │ │ ├── 20250308203912_synced_lyrics_column │ │ │ └── migration.sql │ │ ├── 20250313195107_discs │ │ │ └── migration.sql │ │ ├── 20250426090635_playlist_visibility │ │ │ └── migration.sql │ │ ├── 20250504082315_labels │ │ │ └── migration.sql │ │ ├── 20250505171258_mixed_tag │ │ │ └── migration.sql │ │ ├── 20250518085251_scrobblers │ │ │ └── migration.sql │ │ └── migration_lock.toml │ ├── schema.prisma │ └── types.d.ts ├── sonar-project.properties ├── src │ ├── album │ │ ├── album.controller.spec.ts │ │ ├── album.controller.ts │ │ ├── album.exceptions.ts │ │ ├── album.module.ts │ │ ├── album.service.spec.ts │ │ ├── album.service.ts │ │ └── models │ │ │ ├── album.model.ts │ │ │ ├── album.query-parameters.ts │ │ │ ├── album.response.ts │ │ │ └── update-album.dto.ts │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.plugins.ts │ ├── artist │ │ ├── artist.controller.spec.ts │ │ ├── artist.controller.ts │ │ ├── artist.exceptions.ts │ │ ├── artist.module.ts │ │ ├── artist.service.spec.ts │ │ ├── artist.service.ts │ │ └── models │ │ │ ├── artist.model.ts │ │ │ ├── artist.query-parameters.ts │ │ │ └── artist.response.ts │ ├── authentication │ │ ├── api_key.service.ts │ │ ├── api_key │ │ │ ├── api_key.guard.ts │ │ │ └── api_key.service.ts │ │ ├── authentication.controller.spec.ts │ │ ├── authentication.controller.ts │ │ ├── authentication.exception.ts │ │ ├── authentication.module.ts │ │ ├── authentication.service.ts │ │ ├── jwt │ │ │ ├── jwt-auth.guard.ts │ │ │ ├── jwt-middleware.ts │ │ │ └── jwt.strategy.ts │ │ ├── models │ │ │ ├── auth.enum.ts │ │ │ ├── jwt.models.ts │ │ │ └── login.dto.ts │ │ └── roles │ │ │ ├── roles.decorators.ts │ │ │ ├── roles.enum.ts │ │ │ └── roles.guard.ts │ ├── constants │ │ └── compilation.ts │ ├── events │ │ ├── events.module.ts │ │ └── events.service.ts │ ├── exceptions │ │ ├── all-exceptions.filter.ts │ │ ├── meelo-exception.filter.ts │ │ ├── meelo-exception.ts │ │ ├── not-found.exception.ts │ │ └── orm-exceptions.ts │ ├── external-metadata │ │ ├── external-metadata.controller.spec.ts │ │ ├── external-metadata.controller.ts │ │ ├── external-metadata.exceptions.ts │ │ ├── external-metadata.module.ts │ │ ├── external-metadata.service.ts │ │ ├── models │ │ │ ├── external-metadata.dto.ts │ │ │ ├── external-metadata.query-parameters.ts │ │ │ ├── external-metadata.response.ts │ │ │ ├── provider.dto.ts │ │ │ └── provider.query-parameters.ts │ │ ├── provider.controller.spec.ts │ │ ├── provider.controller.ts │ │ └── provider.service.ts │ ├── file-manager │ │ ├── file-manager.exceptions.ts │ │ ├── file-manager.module.ts │ │ └── file-manager.service.ts │ ├── file │ │ ├── file.controller.spec.ts │ │ ├── file.controller.ts │ │ ├── file.exceptions.ts │ │ ├── file.module.ts │ │ ├── file.service.spec.ts │ │ ├── file.service.ts │ │ └── models │ │ │ ├── file-deletion.dto.ts │ │ │ └── file.query-parameters.ts │ ├── filter │ │ └── filter.ts │ ├── genre │ │ ├── genre.controller.spec.ts │ │ ├── genre.controller.ts │ │ ├── genre.exceptions.ts │ │ ├── genre.module.ts │ │ ├── genre.service.spec.ts │ │ ├── genre.service.ts │ │ └── models │ │ │ └── genre.query-parameters.ts │ ├── housekeeping │ │ ├── housekeeping.module.ts │ │ └── housekeeping.service.ts │ ├── identifier │ │ ├── identifier.exceptions.ts │ │ ├── identifier.pipe.ts │ │ ├── identifier.transform.ts │ │ └── models │ │ │ └── identifier.ts │ ├── illustration │ │ ├── illustration.controller.spec.ts │ │ ├── illustration.controller.ts │ │ ├── illustration.exceptions.ts │ │ ├── illustration.module.ts │ │ ├── illustration.repository.ts │ │ ├── illustration.service.spec.ts │ │ ├── illustration.service.ts │ │ └── models │ │ │ ├── illustration-dimensions.dto.ts │ │ │ ├── illustration-dl.dto.ts │ │ │ ├── illustration-path.model.ts │ │ │ ├── illustration-quality.ts │ │ │ ├── illustration-registration.dto.ts │ │ │ ├── illustration-stats.ts │ │ │ └── illustration.response.ts │ ├── label │ │ ├── label.controller.spec.ts │ │ ├── label.controller.ts │ │ ├── label.exceptions.ts │ │ ├── label.module.ts │ │ ├── label.query-parameters.ts │ │ └── label.service.ts │ ├── library │ │ ├── library.controller.spec.ts │ │ ├── library.controller.ts │ │ ├── library.exceptions.ts │ │ ├── library.module.ts │ │ ├── library.service.spec.ts │ │ ├── library.service.ts │ │ └── models │ │ │ ├── create-library.dto.ts │ │ │ ├── library.query-parameters.ts │ │ │ └── update-library.dto.ts │ ├── logger │ │ ├── logger.module.ts │ │ └── logger.ts │ ├── lyrics │ │ ├── lyrics.exceptions.ts │ │ ├── lyrics.module.ts │ │ ├── lyrics.service.spec.ts │ │ ├── lyrics.service.ts │ │ └── models │ │ │ ├── lyrics.query-parameters.ts │ │ │ ├── lyrics.response.ts │ │ │ └── update-lyrics.dto.ts │ ├── main.ts │ ├── pagination │ │ ├── models │ │ │ ├── paginated-response.spec.ts │ │ │ ├── paginated-response.ts │ │ │ └── pagination-parameters.ts │ │ ├── paginated-response.decorator.ts │ │ └── pagination.exceptions.ts │ ├── parser │ │ ├── parser.module.ts │ │ ├── parser.service.spec.ts │ │ └── parser.service.ts │ ├── playlist │ │ ├── models │ │ │ ├── playlist-entry.model.ts │ │ │ ├── playlist.dto.ts │ │ │ ├── playlist.query-parameters.ts │ │ │ └── playlist.response.ts │ │ ├── playlist.controller.spec.ts │ │ ├── playlist.controller.ts │ │ ├── playlist.exceptions.ts │ │ ├── playlist.module.ts │ │ ├── playlist.service.spec.ts │ │ └── playlist.service.ts │ ├── prisma │ │ ├── prisma.module.ts │ │ └── prisma.service.ts │ ├── registration │ │ ├── metadata.service.ts │ │ ├── models │ │ │ ├── metadata-saved.dto.ts │ │ │ ├── metadata.dto.ts │ │ │ └── metadata.ts │ │ ├── registration.controller.spec.ts │ │ ├── registration.controller.ts │ │ ├── registration.module.ts │ │ ├── registration.service.spec.ts │ │ └── registration.service.ts │ ├── relation-include │ │ ├── atomic-relation-include.filter.ts │ │ ├── models │ │ │ └── relation-include.ts │ │ ├── relation-include-query.decorator.ts │ │ ├── relation-include-route.decorator.ts │ │ ├── relation-include.exceptions.ts │ │ └── relation-include.pipe.ts │ ├── release │ │ ├── models │ │ │ ├── reassign-release.dto.ts │ │ │ ├── release.query-parameters.ts │ │ │ └── release.response.ts │ │ ├── release.controller.spec.ts │ │ ├── release.controller.ts │ │ ├── release.exceptions.ts │ │ ├── release.module.ts │ │ ├── release.service.spec.ts │ │ └── release.service.ts │ ├── repository │ │ ├── repository.utils.ts │ │ └── searchable-repository.service.ts │ ├── response │ │ ├── interceptors │ │ │ ├── array-response.interceptor.ts │ │ │ ├── page-response.interceptor.ts │ │ │ └── response.interceptor.ts │ │ ├── response-type.enum.ts │ │ └── response.decorator.ts │ ├── scrobbler │ │ ├── models │ │ │ ├── lastfm.dto.ts │ │ │ ├── listenbrainz.dto.ts │ │ │ └── scrobblers.response.ts │ │ ├── scrobbler.controller.spec.ts │ │ ├── scrobbler.controller.ts │ │ ├── scrobbler.exceptions.ts │ │ ├── scrobbler.module.ts │ │ ├── scrobbler.service.ts │ │ └── scrobblers │ │ │ ├── lastfm.scrobbler.ts │ │ │ ├── listenbrainz.scrobbler.ts │ │ │ └── scrobbler.ts │ ├── search │ │ ├── models │ │ │ └── create-search-history-entry.dto.ts │ │ ├── search-history.controller.spec.ts │ │ ├── search-history.controller.ts │ │ ├── search-history.service.ts │ │ ├── search.controller.ts │ │ ├── search.exceptions.ts │ │ ├── search.module.ts │ │ ├── search.service.ts │ │ └── search.utils.ts │ ├── settings │ │ ├── models │ │ │ └── settings.ts │ │ ├── settings.controller.spec.ts │ │ ├── settings.controller.ts │ │ ├── settings.exception.ts │ │ ├── settings.module.ts │ │ ├── settings.service.spec.ts │ │ └── settings.service.ts │ ├── slug │ │ ├── slug.spec.ts │ │ └── slug.ts │ ├── song │ │ ├── models │ │ │ ├── merge-song.dto.ts │ │ │ ├── song-group.query-params.ts │ │ │ ├── song-group.response.ts │ │ │ ├── song.query-params.ts │ │ │ ├── song.response.ts │ │ │ └── update-song.dto.ts │ │ ├── song-group.controller.spec.ts │ │ ├── song-group.controller.ts │ │ ├── song-group.service.ts │ │ ├── song.controller.spec.ts │ │ ├── song.controller.ts │ │ ├── song.exceptions.ts │ │ ├── song.module.ts │ │ ├── song.service.spec.ts │ │ └── song.service.ts │ ├── sort │ │ └── models │ │ │ ├── sorting-order.ts │ │ │ └── sorting-parameter.ts │ ├── stream │ │ ├── stream.controller.ts │ │ ├── stream.module.ts │ │ └── stream.service.ts │ ├── swagger │ │ └── bootstrap.ts │ ├── track │ │ ├── models │ │ │ ├── track.query-parameters.ts │ │ │ ├── track.response.ts │ │ │ └── update-track.dto.ts │ │ ├── track.controller.spec.ts │ │ ├── track.controller.ts │ │ ├── track.exceptions.ts │ │ ├── track.module.ts │ │ ├── track.service.spec.ts │ │ └── track.service.ts │ ├── user │ │ ├── models │ │ │ ├── create-user.dto.ts │ │ │ ├── update-user.dto.ts │ │ │ ├── user.model.ts │ │ │ ├── user.query-params.ts │ │ │ └── user.response.ts │ │ ├── user.controller.spec.ts │ │ ├── user.controller.ts │ │ ├── user.exceptions.ts │ │ ├── user.module.ts │ │ ├── user.service.spec.ts │ │ └── user.service.ts │ ├── utils │ │ ├── escape-regex.ts │ │ ├── is-fulfilled.ts │ │ ├── is-undefined.ts │ │ ├── search-date-input.ts │ │ ├── search-string-input.ts │ │ └── shuffle.ts │ └── video │ │ ├── models │ │ ├── update-video.dto.ts │ │ ├── video.query-parameters.ts │ │ └── video.response.ts │ │ ├── video.controller.spec.ts │ │ ├── video.controller.ts │ │ ├── video.exceptions.ts │ │ ├── video.module.ts │ │ ├── video.service.spec.ts │ │ └── video.service.ts ├── test │ ├── assets │ │ ├── artwork.jpeg │ │ ├── cover.jpg │ │ ├── cover1.jpg │ │ ├── cover2.jpg │ │ └── dreams.m4a │ ├── expected-responses.ts │ ├── sequencer.ts │ ├── setup-app.ts │ ├── test-module.ts │ └── test-prisma.service.ts ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock └── settings.json /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules/ 2 | **/dist 3 | **/.env.example 4 | .git 5 | settings.json -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Tell us about a bug or an unexpected behaviour 4 | title: '' 5 | labels: Bug 6 | assignees: Arthi-chaud 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Version** 27 | - [ ] Does this happen on the latest version of the application? 28 | 29 | **Additional context** 30 | Add any other context about the problem here. 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: Feature 6 | assignees: Arthi-chaud 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/actions/cache-front/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Cache (Front)' 2 | description: 'Cache for Front' 3 | runs: 4 | using: "composite" 5 | steps: 6 | - name: Add build dependencies to container 7 | shell: sh 8 | run: apk add --update --no-progress tar 9 | - name: Cache 10 | uses: actions/cache@v3 11 | with: 12 | path: | 13 | ./front/node_modules 14 | ./front/apps/web/.next 15 | key: front-${{ hashFiles('front/yarn.lock') }} 16 | restore-keys: front- 17 | -------------------------------------------------------------------------------- /.github/actions/cache-matcher/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Cache (Matcher)' 2 | description: 'Cache for Matcher' 3 | runs: 4 | using: "composite" 5 | steps: 6 | - run: apk add --update --no-progress lsb-release python3 py3-pip tar 7 | shell: sh 8 | - run: mkdir ./pip-cache 9 | shell: sh 10 | - name: Cache 11 | uses: actions/cache@v3 12 | with: 13 | path: | 14 | ./pip-cache 15 | key: matcher-${{ hashFiles('matcher/requirements.txt') }} 16 | restore-keys: matcher- 17 | - run: pip3 install -r matcher/requirements.txt 18 | shell: sh 19 | env: 20 | PIP_CACHE_DIR: ./pip-cache 21 | -------------------------------------------------------------------------------- /.github/actions/cache-scanner/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Cache (Scanner)' 2 | description: 'Cache for Scanner' 3 | runs: 4 | using: "composite" 5 | steps: 6 | - run: | 7 | cd scanner 8 | go install github.com/swaggo/swag/cmd/swag@latest 9 | ~/go/bin/swag init -d app -o ./app/docs 10 | shell: bash 11 | - uses: actions/setup-go@v5 12 | with: 13 | check-latest: true 14 | go-version-file: 'scanner/go.mod' 15 | cache-dependency-path: scanner/go.sum 16 | go-version: '1.24.1' 17 | -------------------------------------------------------------------------------- /.github/actions/cache-server/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Cache (Server)' 2 | description: 'Cache for Server' 3 | runs: 4 | using: "composite" 5 | steps: 6 | - name: Add build dependencies to container 7 | shell: sh 8 | run: apk add --update --no-progress tar 9 | - name: Cache 10 | uses: actions/cache@v3 11 | with: 12 | path: | 13 | ./server/node_modules 14 | ./server/dist 15 | key: server-${{ hashFiles('server/yarn.lock') }} 16 | restore-keys: server- 17 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | # Source: https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes 2 | 3 | changelog: 4 | categories: 5 | - title: Exciting New Features 🎉 6 | labels: 7 | - Feature 8 | - Basic 9 | - title: Improvements, Optmizations & Enhancements 👍 10 | labels: 11 | - Enhancement/Optimization 12 | - Layout 13 | - title: Fixes & Patches 🩹 14 | labels: 15 | - Bug 16 | - Fixes 17 | - title: Documentation 📖 18 | labels: 19 | - Documentation 20 | - title: Security 🛡 21 | labels: 22 | - Security 23 | - title: Deployment ⚙ 24 | labels: 25 | - CI/CD 26 | - title: Other Changes 🔀 27 | labels: 28 | - "*" 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/dist 3 | # OS 4 | .DS_Store 5 | 6 | # IDEs and editors 7 | /.idea 8 | .project 9 | .classpath 10 | .c9/ 11 | *.launch 12 | .settings/ 13 | *.sublime-workspace 14 | 15 | # IDE - VSCode 16 | .vscode/ 17 | 18 | docs/generated 19 | 20 | meelo/ 21 | data/ 22 | 23 | .env -------------------------------------------------------------------------------- /assets/examples/album-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/assets/examples/album-page.png -------------------------------------------------------------------------------- /assets/examples/album-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/assets/examples/album-top.png -------------------------------------------------------------------------------- /assets/examples/artist-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/assets/examples/artist-page.png -------------------------------------------------------------------------------- /assets/examples/live-albums.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/assets/examples/live-albums.png -------------------------------------------------------------------------------- /assets/examples/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/assets/examples/player.png -------------------------------------------------------------------------------- /assets/examples/releases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/assets/examples/releases.png -------------------------------------------------------------------------------- /assets/examples/song-groups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/assets/examples/song-groups.png -------------------------------------------------------------------------------- /assets/examples/synced-lyrics.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/assets/examples/synced-lyrics.gif -------------------------------------------------------------------------------- /assets/examples/versions-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/assets/examples/versions-page.png -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "vcs": { 4 | "enabled": false, 5 | "clientKind": "git", 6 | "useIgnoreFile": true, 7 | "defaultBranch": "main" 8 | }, 9 | "files": { 10 | "ignore": ["**/.yarn/**", "**/dist/**", "**/package.json"] 11 | }, 12 | "formatter": { 13 | "enabled": true, 14 | "indentStyle": "tab", 15 | "indentWidth": 4 16 | }, 17 | "organizeImports": { 18 | "enabled": true 19 | }, 20 | "linter": { 21 | "enabled": true, 22 | "rules": { 23 | "recommended": true, 24 | "style": { 25 | "noParameterAssign": "off" 26 | }, 27 | "performance": { 28 | "noAccumulatingSpread": "off" 29 | }, 30 | "complexity": { 31 | "noBannedTypes": "off" 32 | }, 33 | "suspicious": { 34 | "noExplicitAny": "off", 35 | "noConsole": "error" 36 | }, 37 | "correctness": { 38 | "noUnusedImports": "warn" 39 | } 40 | } 41 | }, 42 | "javascript": { 43 | "formatter": { 44 | "quoteStyle": "double" 45 | }, 46 | "parser": { 47 | "unsafeParameterDecoratorsEnabled": true 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /front/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | apps/web/.next/ 3 | -------------------------------------------------------------------------------- /front/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | .next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | .pnp.* 39 | .yarn/* 40 | !.yarn/patches 41 | !.yarn/plugins 42 | !.yarn/releases 43 | !.yarn/sdks 44 | -------------------------------------------------------------------------------- /front/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.4.0.cjs 4 | -------------------------------------------------------------------------------- /front/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine3.18 2 | WORKDIR /app 3 | 4 | COPY ./.yarnrc.yml ./*.lock ./ 5 | COPY ./.yarn ./.yarn 6 | COPY package.json tsconfig.base.json ./ 7 | COPY apps/web/*.json apps/web/ 8 | COPY packages/*.json packages/ 9 | COPY packages/api/*.json packages/api/ 10 | COPY packages/models/*.json packages/models/ 11 | COPY packages/state/*.json packages/state/ 12 | COPY packages/utils/*.json packages/utils/ 13 | 14 | RUN yarn install 15 | 16 | ENV PORT 3000 17 | ENV HOSTNAME "0.0.0.0" 18 | 19 | CMD yarn && yarn run start:dev 20 | -------------------------------------------------------------------------------- /front/apps/web/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const config = { 3 | output: "standalone", 4 | reactStrictMode: false, 5 | swcMinify: true, 6 | i18n: { 7 | locales: ["en", "fr"], 8 | defaultLocale: "en", 9 | }, 10 | async redirects() { 11 | return [ 12 | { 13 | source: "/songs/:slug", 14 | destination: "/songs/:slug/lyrics", 15 | permanent: true, 16 | }, 17 | ]; 18 | }, 19 | webpack: (config) => { 20 | config.resolve.extensions = [ 21 | ".web.ts", 22 | ".web.tsx", 23 | ...config.resolve.extensions, 24 | ]; 25 | return config; 26 | }, 27 | transpilePackages: ["models", "api", "state", "utils", "components"], 28 | experimental: { 29 | externalDir: true, 30 | }, 31 | }; 32 | 33 | if (process.env.NODE_ENV !== "production") { 34 | config.rewrites = async () => [ 35 | { 36 | source: "/api/:path*", 37 | destination: process.env.SSR_SERVER_URL 38 | ? `${process.env.SSR_SERVER_URL}/:path*` 39 | : "/api/:path*", 40 | }, 41 | { 42 | source: "/scanner/:path*", 43 | destination: process.env.SSR_SCANNER_URL 44 | ? `${process.env.SSR_SCANNER_URL}/:path*` 45 | : "/scanner/:path*", 46 | }, 47 | ]; 48 | } 49 | 50 | module.exports = config; 51 | -------------------------------------------------------------------------------- /front/apps/web/public: -------------------------------------------------------------------------------- 1 | ../../assets -------------------------------------------------------------------------------- /front/apps/web/src/components/head.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { AppName } from "@/utils/constants"; 20 | import NextHead from "next/head"; 21 | 22 | export const Head = ({ 23 | title, 24 | description, 25 | }: { 26 | title?: string | false; 27 | description?: string; 28 | }) => { 29 | const formattedTitle = !title ? AppName : `${title} | ${AppName}`; 30 | return ( 31 | 32 | {formattedTitle} 33 | 34 | {description && } 35 | 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { useTranslation } from "react-i18next"; 20 | import ErrorPage from "~/components/error-page"; 21 | 22 | const PageNotFound = () => { 23 | const { t } = useTranslation(); 24 | 25 | return ; 26 | }; 27 | 28 | export default PageNotFound; 29 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/500.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { useTranslation } from "react-i18next"; 20 | import ErrorPage from "~/components/error-page"; 21 | 22 | const InternalError = () => { 23 | const { t } = useTranslation(); 24 | 25 | return ; 26 | }; 27 | 28 | export default InternalError; 29 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/albums/compilations.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import AlbumsPage from "./index"; 20 | // routing checks are done there 21 | export default AlbumsPage; 22 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/artists/[slugOrId]/rare-songs.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import ArtistSongPage from "./songs"; 20 | // routing checks are done there 21 | export default ArtistSongPage; 22 | -------------------------------------------------------------------------------- /front/apps/web/src/pages/artists/index.tsx: -------------------------------------------------------------------------------- 1 | import { getArtists } from "@/api/queries"; 2 | import { 3 | getOrderQuery, 4 | getSortQuery, 5 | } from "@/components/infinite/controls/sort"; 6 | import InfiniteArtistView from "@/components/infinite/resource/artist"; 7 | import { ArtistSortingKeys } from "@/models/artist"; 8 | import type { NextPageContext } from "next"; 9 | import { useTranslation } from "react-i18next"; 10 | import type { GetPropsTypesFrom, Page } from "ssr"; 11 | import { Head } from "~/components/head"; 12 | 13 | const prepareSSR = (context: NextPageContext) => { 14 | const order = getOrderQuery(context) ?? "asc"; 15 | const sortBy = getSortQuery(context, ArtistSortingKeys); 16 | 17 | return { 18 | infiniteQueries: [getArtists({}, { sortBy, order }, ["illustration"])], 19 | }; 20 | }; 21 | 22 | const ArtistsPage: Page> = () => { 23 | const { t } = useTranslation(); 24 | 25 | return ( 26 | <> 27 | 28 | 30 | getArtists({ library: libraries }, { sortBy, order }, [ 31 | "illustration", 32 | ]) 33 | } 34 | /> 35 | 36 | ); 37 | }; 38 | 39 | ArtistsPage.prepareSSR = prepareSSR; 40 | 41 | export default ArtistsPage; 42 | -------------------------------------------------------------------------------- /front/apps/web/src/theme/font.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { Rubik } from "next/font/google"; 20 | 21 | export const FontVariable = "--font-rubik" as const; 22 | 23 | const Font = Rubik({ 24 | subsets: ["latin"], 25 | preload: true, 26 | display: "swap", 27 | fallback: ["Rubik", "Helvetica", "Arial", "sans-serif"], 28 | variable: "--font-rubik", 29 | }); 30 | 31 | export default Font; 32 | -------------------------------------------------------------------------------- /front/apps/web/src/theme/styles.css: -------------------------------------------------------------------------------- 1 | /* For Background transition */ 2 | @property --gradientColor1 { 3 | syntax: ""; 4 | initial-value: #000000; 5 | inherits: true; 6 | } 7 | 8 | @property --gradientColor2 { 9 | syntax: ""; 10 | initial-value: #000000; 11 | inherits: true; 12 | } 13 | 14 | @property --gradientColor3 { 15 | syntax: ""; 16 | initial-value: #000000; 17 | inherits: true; 18 | } 19 | 20 | @property --gradientColor4 { 21 | syntax: ""; 22 | initial-value: #000000; 23 | inherits: true; 24 | } 25 | 26 | @property --gradientColor5 { 27 | syntax: ""; 28 | initial-value: #000000; 29 | inherits: true; 30 | } 31 | -------------------------------------------------------------------------------- /front/apps/web/src/utils/accent-color.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import type Illustration from "@/models/illustration"; 20 | 21 | export const useAccentColor = (illustration?: Illustration | null) => { 22 | const sortedColors = Array.of(...(illustration?.colors ?? [])).sort(); 23 | 24 | if (sortedColors.length < 3) { 25 | return null; 26 | } 27 | return { 28 | light: Array.of(...sortedColors) 29 | .reverse() 30 | .at(3)!, 31 | dark: sortedColors.at(3)!, 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /front/apps/web/src/utils/getSlugOrId.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | const getSlugOrId = (params: any) => { 20 | return params.slugOrId as string; 21 | }; 22 | 23 | export default getSlugOrId; 24 | -------------------------------------------------------------------------------- /front/apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../tsconfig.base.json"], 3 | "compilerOptions": { 4 | "baseUrl": "src/", 5 | "paths": { 6 | "~/*": ["./*"], 7 | "@/models/*": ["../../../packages/models/src/*"], 8 | "@/utils/*": ["../../../packages/utils/src/*"], 9 | "@/api": ["../../../packages/api/src/index"], 10 | "@/api/*": ["../../../packages/api/src/*"], 11 | "@/state/*": ["../../../packages/state/src/*"], 12 | "@/components/*": ["../../../packages/components/src/*"] 13 | } 14 | }, 15 | "include": [ 16 | "next-env.d.ts", 17 | "**/*.ts", 18 | "**/*.tsx", 19 | "../../packages/utils/src/*.d.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /front/assets/banner1-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/front/assets/banner1-black.png -------------------------------------------------------------------------------- /front/assets/banner1-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/front/assets/banner1-white.png -------------------------------------------------------------------------------- /front/assets/banner2-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/front/assets/banner2-black.png -------------------------------------------------------------------------------- /front/assets/banner2-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/front/assets/banner2-white.png -------------------------------------------------------------------------------- /front/assets/favicon-black.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/front/assets/favicon-black.ico -------------------------------------------------------------------------------- /front/assets/favicon-white.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/front/assets/favicon-white.ico -------------------------------------------------------------------------------- /front/assets/icon-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/front/assets/icon-black.png -------------------------------------------------------------------------------- /front/assets/icon-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/front/assets/icon-white.png -------------------------------------------------------------------------------- /front/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../biome.json"], 3 | "formatter": { 4 | "lineWidth": 80 5 | }, 6 | "files": { 7 | "ignore": ["**/.next/**", "translations/*.json"] 8 | }, 9 | "linter": { 10 | "rules": { 11 | "style": { 12 | "noNonNullAssertion": "off" 13 | }, 14 | "suspicious": { 15 | "noExtraNonNullAssertion": "warn", 16 | "noArrayIndexKey": "off" 17 | }, 18 | "correctness": { 19 | "useExhaustiveDependencies": "off", 20 | "noUnusedVariables": "warn" 21 | }, 22 | "complexity": { 23 | "noForEach": "off" 24 | }, 25 | "nursery": { 26 | "noRestrictedImports": { 27 | "options": { 28 | "paths": { 29 | "format-duration": "Use wrapper from 'theme/provider'", 30 | "@mui/icons-material": "Use icons in 'src/components/icons'" 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /front/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meelo", 3 | "private": true, 4 | "scripts": { 5 | "start:web:dev": "yarn workspace web run start:dev", 6 | "start:web:prod": "yarn workspace web run start:prod", 7 | "tsc:web": "yarn workspace web run tsc", 8 | "tsc:all": "yarn workspaces foreach -A --exclude 'meelo' run tsc", 9 | "build:web": "yarn workspace web build", 10 | "lint": "biome lint .", 11 | "format": "biome format ." 12 | }, 13 | "workspaces": [ 14 | "apps/*", 15 | "packages/*" 16 | ], 17 | "devDependencies": { 18 | "@biomejs/biome": "1.9.4", 19 | "jest": "^29.7.0", 20 | "typescript": "5.3.3" 21 | }, 22 | "packageManager": "yarn@4.4.0" 23 | } 24 | -------------------------------------------------------------------------------- /front/packages/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "sideEffects": false, 4 | "dependencies": { 5 | "@tanstack/react-query": "^5.77.0", 6 | "jotai": "^2.12.1", 7 | "react": "18.2.0", 8 | "type-fest": "^4.33.0", 9 | "yup": "^1.0.0" 10 | }, 11 | "devDependencies": { 12 | "typescript": "5.3.3" 13 | }, 14 | "packageManager": "yarn@4.4.0" 15 | } 16 | -------------------------------------------------------------------------------- /front/packages/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../tsconfig.base.json"], 3 | "include": ["**/*.ts"], 4 | "exclude": ["node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /front/packages/components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "components", 3 | "sideEffects": false, 4 | "dependencies": { 5 | "iconsax-react": "^0.0.8" 6 | }, 7 | "peerDependencies": { 8 | "@mui/material": "*", 9 | "next": "*", 10 | "react": "*", 11 | "react-i18next": "*" 12 | }, 13 | "devDependencies": { 14 | "typescript": "5.3.3" 15 | }, 16 | "packageManager": "yarn@4.4.0" 17 | } 18 | -------------------------------------------------------------------------------- /front/packages/components/src/_internal.ts: -------------------------------------------------------------------------------- 1 | // Needs this for correct typing of MUI's theme 2 | // https://mui.com/material-ui/customization/css-theme-variables/usage/#typescript 3 | import type {} from "@mui/material/themeCssVarsAugmentation"; 4 | -------------------------------------------------------------------------------- /front/packages/components/src/actions/auth.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { LogoutIcon } from "@/components/icons"; 20 | import { store } from "@/state/store"; 21 | import { accessTokenAtom } from "@/state/user"; 22 | import type Action from "./"; 23 | 24 | export const LogoutAction: Action = { 25 | label: "actions.logout", 26 | icon: , 27 | href: "/", 28 | onClick: () => store.set(accessTokenAtom, undefined), 29 | }; 30 | -------------------------------------------------------------------------------- /front/packages/components/src/actions/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import type { RequireExactlyOne } from "type-fest"; 20 | 21 | /** 22 | * Props for a generic component to run an action/go to page 23 | */ 24 | type Action = { 25 | disabled?: boolean; 26 | onClick?: () => void; 27 | label: TranslationKey; 28 | icon?: JSX.Element; 29 | } & Partial< 30 | RequireExactlyOne<{ 31 | href: string; 32 | dialog: (controls: { close: () => void }) => JSX.Element; 33 | }> 34 | >; 35 | 36 | export default Action; 37 | -------------------------------------------------------------------------------- /front/packages/components/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../tsconfig.base.json"], 3 | "include": ["**/*.ts", "**/*.tsx", "../utils/src/*.d.ts"], 4 | "compilerOptions": { 5 | "jsx": "react-jsx" 6 | }, 7 | "exclude": ["node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /front/packages/models/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "models", 3 | "sideEffects": false, 4 | "devDependencies": { 5 | "typescript": "5.3.3" 6 | }, 7 | "packageManager": "yarn@4.4.0" 8 | } 9 | -------------------------------------------------------------------------------- /front/packages/models/src/disc.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as yup from "yup"; 20 | 21 | export const Disc = yup.object({ 22 | index: yup.number().required().nullable(), 23 | name: yup.string().required().nullable(), 24 | }); 25 | 26 | export type Disc = yup.InferType; 27 | -------------------------------------------------------------------------------- /front/packages/models/src/exceptions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export class MeeloException extends Error {} 20 | 21 | /** 22 | * Exception on resource not found 23 | * If caught by ErrorBoundary, renders 404 page 24 | */ 25 | export class ResourceNotFound extends MeeloException {} 26 | -------------------------------------------------------------------------------- /front/packages/models/src/file.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as yup from "yup"; 20 | import Resource from "./resource"; 21 | import { yupdate } from "./utils"; 22 | 23 | const File = Resource.concat( 24 | yup.object({ 25 | /** 26 | * Path of the track, relative to the parent library 27 | */ 28 | path: yup.string().required(), 29 | /** 30 | * Date of the file registration 31 | */ 32 | registerDate: yupdate.required(), 33 | /** 34 | * ID of the library 35 | */ 36 | libraryId: yup.number().required(), 37 | }), 38 | ); 39 | 40 | type File = yup.InferType; 41 | 42 | export default File; 43 | -------------------------------------------------------------------------------- /front/packages/models/src/genre.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as yup from "yup"; 20 | import Resource from "./resource"; 21 | 22 | const Genre = Resource.concat( 23 | yup.object({ 24 | /** 25 | * Name of the genre 26 | */ 27 | name: yup.string().required(), 28 | /** 29 | * Unique identifier as a string 30 | */ 31 | slug: yup.string().required(), 32 | }), 33 | ); 34 | 35 | type Genre = yup.InferType; 36 | 37 | export default Genre; 38 | 39 | export const GenreSortingKeys = ["name", "songCount"] as const; 40 | -------------------------------------------------------------------------------- /front/packages/models/src/label.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as yup from "yup"; 20 | import Resource from "./resource"; 21 | 22 | /** 23 | * 'Instance' of a song on a release 24 | */ 25 | const Label = Resource.concat( 26 | yup.object({ 27 | slug: yup.string().required(), 28 | name: yup.string().required(), 29 | }), 30 | ); 31 | 32 | type Label = yup.InferType; 33 | 34 | export default Label; 35 | 36 | export const LabelSortingKeys = ["name", "releaseCount"] as const; 37 | -------------------------------------------------------------------------------- /front/packages/models/src/layout.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export const LayoutOptions = ["grid", "list"] as const; 20 | 21 | export type LayoutOption = (typeof LayoutOptions)[number]; 22 | 23 | export const ItemSize = ["xs", "s", "m", "l", "xl"] as const; 24 | 25 | export type ItemSize = (typeof ItemSize)[number]; 26 | 27 | export const DefaultItemSize = "m" satisfies ItemSize; 28 | -------------------------------------------------------------------------------- /front/packages/models/src/library.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as yup from "yup"; 20 | import Resource from "./resource"; 21 | 22 | const Library = Resource.concat( 23 | yup.object({ 24 | /** 25 | * Title of the library 26 | */ 27 | name: yup.string().required(), 28 | /** 29 | * Slug of the library 30 | * Unique identifier 31 | */ 32 | slug: yup.string().required(), 33 | /** 34 | * Path of the library 35 | */ 36 | path: yup.string().required(), 37 | }), 38 | ); 39 | 40 | type Library = yup.InferType; 41 | 42 | export default Library; 43 | -------------------------------------------------------------------------------- /front/packages/models/src/lyrics.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as yup from "yup"; 20 | 21 | export const SyncedLyric = yup.object({ 22 | timestamp: yup.number().required(), 23 | content: yup.string().nonNullable(), 24 | }); 25 | 26 | export type SyncedLyric = yup.InferType; 27 | 28 | export const Lyrics = yup.object({ 29 | plain: yup.string().required(), 30 | synced: yup.array(SyncedLyric.required()).required().nullable(), 31 | }); 32 | 33 | export type Lyrics = yup.InferType; 34 | -------------------------------------------------------------------------------- /front/packages/models/src/resource.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as yup from "yup"; 20 | 21 | const Resource = yup.object({ 22 | /** 23 | * Unique identifier 24 | */ 25 | id: yup.number().required(), 26 | }); 27 | 28 | type Resource = yup.InferType; 29 | 30 | export default Resource; 31 | -------------------------------------------------------------------------------- /front/packages/models/src/sorting.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export const Orders = ["asc", "desc"] as const; 20 | 21 | export type Order = (typeof Orders)[number]; 22 | 23 | export type SortingParameters = { 24 | sortBy: Keys[number]; 25 | order?: Order; 26 | }; 27 | -------------------------------------------------------------------------------- /front/packages/models/src/task.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as yup from "yup"; 20 | 21 | export const Task = yup.object({ 22 | name: yup.string().required(), 23 | description: yup.string().required(), 24 | }); 25 | 26 | export type Task = yup.InferType; 27 | 28 | export const TaskResponse = yup.object({ 29 | /** 30 | * Status of the task 31 | */ 32 | message: yup.string().required(), 33 | }); 34 | 35 | export type TaskResponse = yup.InferType; 36 | -------------------------------------------------------------------------------- /front/packages/models/src/user.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import * as yup from "yup"; 20 | import Resource from "./resource"; 21 | 22 | const User = Resource.concat( 23 | yup.object({ 24 | name: yup.string().required(), 25 | enabled: yup.boolean().required(), 26 | admin: yup.boolean().required(), 27 | }), 28 | ); 29 | 30 | type User = yup.InferType; 31 | 32 | export const UserSortingKeys = ["id", "name", "admin", "enabled"] as const; 33 | 34 | export default User; 35 | -------------------------------------------------------------------------------- /front/packages/models/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../tsconfig.base.json"], 3 | "include": ["**/*.ts"], 4 | "exclude": ["node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /front/packages/state/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "state", 3 | "sideEffects": false, 4 | "dependencies": { 5 | "jotai": "^2.12.1", 6 | "react": "18.2.0" 7 | }, 8 | "devDependencies": { 9 | "typescript": "5.3.3" 10 | }, 11 | "packageManager": "yarn@4.4.0" 12 | } 13 | -------------------------------------------------------------------------------- /front/packages/state/src/store.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | import { createStore } from "jotai"; 19 | 20 | export const store = createStore(); 21 | -------------------------------------------------------------------------------- /front/packages/state/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../tsconfig.base.json"], 3 | "include": ["**/*.ts"], 4 | "exclude": ["node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /front/packages/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../tsconfig.base.json"], 3 | "compilerOptions": { 4 | "baseUrl": "src/", 5 | "paths": { 6 | "~/*": ["./*"], 7 | "@/models/*": ["../models/src/*"], 8 | "@/state/*": ["../state/src/*"], 9 | "@/utils/*": ["../utils/src/*"], 10 | "@/api": ["../api/src/index"], 11 | "@/api/*": ["../api/src/*"], 12 | "@/components/*": ["../components/src/*"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /front/packages/utils/__tests__/utils/format-duration.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "@jest/globals"; 2 | import formatDuration from "../../src/format-duration"; 3 | 4 | describe("Format Duration", () => { 5 | it("Should format duration correctly", () => { 6 | const duration = 61; 7 | const formatted = formatDuration(duration); 8 | 9 | expect(formatted).toBe("1:01"); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /front/packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "utils", 3 | "sideEffects": false, 4 | "dependencies": { 5 | "format-duration": "^2.0.0", 6 | "i18next": "^23.7.16" 7 | }, 8 | "devDependencies": { 9 | "jest": "^29.7.0", 10 | "typescript": "5.3.3" 11 | }, 12 | "packageManager": "yarn@4.4.0" 13 | } 14 | -------------------------------------------------------------------------------- /front/packages/utils/src/constants.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export const AppName = "Meelo"; 20 | 21 | export const UserAccessTokenStorageKey = "access_token"; 22 | 23 | export const LanguageStorageKey = "meelo_lang"; 24 | 25 | // ID of the component that contains the infinite list 26 | // 27 | // For web only 28 | export const ParentScrollableDivId = "scrollableDiv" as const; 29 | -------------------------------------------------------------------------------- /front/packages/utils/src/copy-link.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import toast from "react-hot-toast"; 20 | 21 | /** 22 | * Copy meelo url to clipboard, with hostname 23 | * @param route the route to copy to clipboard 24 | */ 25 | export default function copyLinkToClipboard(route: string, t: Translator) { 26 | navigator.clipboard.writeText( 27 | `${location.protocol}//${location.host}${route}`, 28 | ); 29 | toast.success(t("toasts.linkCopied")); 30 | } 31 | -------------------------------------------------------------------------------- /front/packages/utils/src/date.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export const getDate = (date: Date | string | null) => { 20 | if (date === null) { 21 | return null; 22 | } 23 | if (typeof date === "string") { 24 | return new Date(date); 25 | } 26 | return date; 27 | }; 28 | 29 | export const getYear = (date: Date | string | null) => 30 | getDate(date)?.getFullYear() ?? null; 31 | -------------------------------------------------------------------------------- /front/packages/utils/src/format-artists.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import type Artist from "@/models/artist"; 20 | 21 | const formatArtists = ( 22 | artist: Pick, 23 | featuring?: Pick[], 24 | ): string => { 25 | if (!featuring || featuring.length === 0) { 26 | return artist.name; 27 | } 28 | const [firstFeat, ...otherFeats] = featuring; 29 | 30 | if (otherFeats.length === 0) { 31 | return `${artist.name} & ${firstFeat.name}`; 32 | } 33 | return `${artist.name}, ${featuring 34 | .map(({ name }) => name) 35 | .slice(0, -1) 36 | .join(", ")} & ${featuring.at(-1)?.name}`; 37 | }; 38 | 39 | export default formatArtists; 40 | -------------------------------------------------------------------------------- /front/packages/utils/src/format-duration.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | // biome-ignore lint/nursery/noRestrictedImports: Internal use 20 | import formatMilliSecondsDuration from "format-duration"; 21 | 22 | const formatDuration = (seconds?: number | null) => 23 | seconds ? formatMilliSecondsDuration(seconds * 1000) : "--:--"; 24 | 25 | export default formatDuration; 26 | -------------------------------------------------------------------------------- /front/packages/utils/src/gen-list.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export const generateArray = (count: number, filler?: T): T[] => 20 | Array(count).fill(filler); 21 | -------------------------------------------------------------------------------- /front/packages/utils/src/i18next.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import type en from "../../../translations/en.json"; 20 | import "i18next"; 21 | 22 | declare module "i18next" { 23 | interface CustomTypeOptions { 24 | defaultNS: "translation"; 25 | returnNull: false; 26 | returnObjects: false; 27 | resources: { 28 | translation: typeof en; 29 | }; 30 | } 31 | } 32 | 33 | declare global { 34 | // https://github.com/i18next/i18next/blob/master/typescript/t.d.ts 35 | type TranslationKey = KeysBuilderWithoutReturnObjects< 36 | CustomTypeOptions["resources"]["translation"] 37 | >; 38 | 39 | type Translator = (key: TranslationKey) => string; 40 | } 41 | -------------------------------------------------------------------------------- /front/packages/utils/src/is-ssr.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export const isSSR = () => typeof window === "undefined"; 20 | 21 | export const isClientSideRendering = () => !isSSR(); 22 | 23 | // TODO For native, it should throw, we shouldn't have to deal with that 24 | -------------------------------------------------------------------------------- /front/packages/utils/src/random.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export const getRandomNumber = () => { 20 | return Math.floor(Math.random() * 10000); 21 | }; 22 | -------------------------------------------------------------------------------- /front/packages/utils/src/uncapitalize.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export const uncapitalize = (s: T): Uncapitalize => { 20 | if (s.length === 0) { 21 | return s as unknown as Uncapitalize; 22 | } 23 | return (s[0].toLowerCase() + s.slice(1)) as Uncapitalize; 24 | }; 25 | -------------------------------------------------------------------------------- /front/packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../tsconfig.base.json"], 3 | "include": ["**/*.ts"], 4 | "exclude": ["node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /front/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=arthi-chaud_Meelo-front 2 | sonar.organization=arthi-chaud 3 | 4 | # This is the name and version displayed in the SonarCloud UI. 5 | sonar.projectName=Meelo (Front) 6 | #sonar.projectVersion=1.0 7 | 8 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 9 | #sonar.sources=. 10 | 11 | # Define the same root directory for sources and tests 12 | sonar.sources = ./apps/web/src,./packages/ 13 | 14 | # Include test subdirectories in test scope 15 | # sonar.test = __tests__/ 16 | 17 | # Exclude test subdirectories from source scope 18 | sonar.exclusions = 19 | 20 | # sonar.typescript.lcov.reportPaths = ./coverage/lcov.info 21 | -------------------------------------------------------------------------------- /front/translations/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import en from "./en.json"; 20 | import fr from "./fr.json"; 21 | import ru from "./ru.json"; 22 | 23 | export default Object.entries({ en, fr, ru }).reduce( 24 | (rest, [key, value]) => ({ ...rest, [key]: { translation: value } }), 25 | {}, 26 | ); 27 | -------------------------------------------------------------------------------- /front/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /matcher/.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__/ 2 | bin/ 3 | lib/ 4 | pyvenv.cfg 5 | -------------------------------------------------------------------------------- /matcher/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.13-alpine 2 | 3 | WORKDIR /app 4 | 5 | COPY ./requirements.txt . 6 | RUN pip3 install -r ./requirements.txt 7 | 8 | COPY . . 9 | 10 | ENTRYPOINT ["python3", "-m"] 11 | CMD ["matcher"] 12 | -------------------------------------------------------------------------------- /matcher/README.md: -------------------------------------------------------------------------------- 1 | # Meelo's Matcher 2 | 3 | This microservice is responsible for getting metadata from external providers (MusicBrainz, Genius, etc.) 4 | 5 | It receives events through a message queue, and pushes the metadata to the API (using the HTTP API). 6 | 7 | ## Configuration 8 | 9 | ### Environment Variables 10 | 11 | - `RABBITMQ_URL`: URL to the RabbitMQ service 12 | - `API_URL`: URL to the API/server service 13 | - `API_KEYS`: List of comma separated keys used by microservices (e.g. scanner, matcher) to authenticate 14 | 15 | For tests, we need additional variables: 16 | - `GENIUS_ACCESS_TOKEN`: Token to authenticate to the Genius Provider 17 | - `DISCOGS_ACCESS_TOKEN`: Token to authenticate to the Discogs Provider 18 | 19 | ### Files 20 | 21 | - `settings.json`: JSON File located in `INTERNAL_CONFIG_DIR`. See user doc for specs. 22 | -------------------------------------------------------------------------------- /matcher/assets/allmusic/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/matcher/assets/allmusic/icon.png -------------------------------------------------------------------------------- /matcher/assets/discogs/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/matcher/assets/discogs/icon.png -------------------------------------------------------------------------------- /matcher/assets/genius/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/matcher/assets/genius/icon.png -------------------------------------------------------------------------------- /matcher/assets/lrclib/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/matcher/assets/lrclib/icon.png -------------------------------------------------------------------------------- /matcher/assets/metacritic/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/matcher/assets/metacritic/icon.png -------------------------------------------------------------------------------- /matcher/assets/musicbrainz/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/matcher/assets/musicbrainz/icon.png -------------------------------------------------------------------------------- /matcher/assets/wikipedia/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/matcher/assets/wikipedia/icon.png -------------------------------------------------------------------------------- /matcher/matcher/context.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List, TypeVar, Type 3 | from matcher.providers.boilerplate import BaseProviderBoilerplate 4 | from .api import API 5 | from .settings import Settings 6 | 7 | T = TypeVar("T", bound=BaseProviderBoilerplate) 8 | 9 | 10 | @dataclass 11 | class _InternalContext: 12 | client: API 13 | settings: Settings 14 | providers: List[BaseProviderBoilerplate] 15 | 16 | def get_provider(self, cl: Type[T]) -> T | None: 17 | for provider in self.providers: 18 | if isinstance(provider, cl): 19 | return provider 20 | return None 21 | 22 | 23 | class Context: 24 | _instance: _InternalContext | None 25 | 26 | @classmethod 27 | def init( 28 | cls, client: API, settings: Settings, providers: List[BaseProviderBoilerplate] 29 | ): 30 | cls._instance = _InternalContext(client, settings, providers) 31 | 32 | @classmethod 33 | def get(cls) -> _InternalContext: 34 | if not cls._instance: 35 | raise Exception("Cannot access context, it is not initialised.") 36 | return cls._instance 37 | -------------------------------------------------------------------------------- /matcher/matcher/models/api/domain.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from dataclasses_json import ( 3 | dataclass_json, 4 | LetterCase, 5 | Undefined, 6 | DataClassJsonMixin, 7 | ) 8 | from typing import Optional, List 9 | from matcher.providers.domain import AlbumType 10 | 11 | 12 | @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) # type: ignore 13 | @dataclass 14 | class Artist(DataClassJsonMixin): 15 | name: str 16 | 17 | 18 | @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) # type: ignore 19 | @dataclass 20 | class Album(DataClassJsonMixin): 21 | name: str 22 | artist: Optional[Artist] = None 23 | type: AlbumType = AlbumType.OTHER 24 | release_date: Optional[str] = None 25 | 26 | 27 | @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) # type: ignore 28 | @dataclass 29 | class Track(DataClassJsonMixin): 30 | source_file_id: int 31 | duration: Optional[int] = None 32 | 33 | 34 | @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) # type: ignore 35 | @dataclass 36 | class Song(DataClassJsonMixin): 37 | name: str 38 | artist: Artist 39 | featuring: List[Artist] 40 | master: Optional[Track] = None 41 | 42 | 43 | @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) # type: ignore 44 | @dataclass 45 | class File(DataClassJsonMixin): 46 | fingerprint: Optional[str] = None 47 | -------------------------------------------------------------------------------- /matcher/matcher/models/api/dto.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List, Optional 3 | from dataclasses_json import DataClassJsonMixin, LetterCase, dataclass_json 4 | 5 | 6 | 7 | @dataclass_json(letter_case=LetterCase.CAMEL) # type: ignore 8 | @dataclass 9 | class UpdateAlbumDto(DataClassJsonMixin): 10 | # str should be iso 8601 11 | release_date: Optional[str] = None 12 | genres: Optional[List[str]] = None 13 | type: Optional[str] = None 14 | 15 | 16 | @dataclass_json 17 | @dataclass 18 | class CreateProviderDto(DataClassJsonMixin): 19 | name: str 20 | 21 | 22 | @dataclass_json(letter_case=LetterCase.CAMEL) # type: ignore 23 | @dataclass 24 | class ExternalMetadataSourceDto(DataClassJsonMixin): 25 | url: str 26 | provider_id: int 27 | 28 | 29 | @dataclass_json(letter_case=LetterCase.CAMEL) # type: ignore 30 | @dataclass 31 | class ExternalMetadataDto(DataClassJsonMixin): 32 | description: str | None 33 | rating: int | None 34 | song_id: int | None 35 | artist_id: int | None 36 | album_id: int | None 37 | sources: List[ExternalMetadataSourceDto] 38 | -------------------------------------------------------------------------------- /matcher/matcher/models/api/page.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from dataclasses_json import dataclass_json 3 | from typing import List, TypeVar, Generic 4 | 5 | T = TypeVar("T") 6 | 7 | 8 | @dataclass_json 9 | @dataclass 10 | class Page(Generic[T]): 11 | items: List[T] 12 | -------------------------------------------------------------------------------- /matcher/matcher/models/api/provider.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from dataclasses_json import DataClassJsonMixin, dataclass_json, LetterCase 3 | from typing import Optional 4 | 5 | 6 | @dataclass_json(letter_case=LetterCase.CAMEL) # type: ignore 7 | @dataclass 8 | class Provider(DataClassJsonMixin): 9 | id: int 10 | name: str 11 | slug: str 12 | illustration_id: Optional[int] 13 | -------------------------------------------------------------------------------- /matcher/matcher/models/event.py: -------------------------------------------------------------------------------- 1 | import jsons 2 | import json 3 | from dataclasses import dataclass 4 | 5 | 6 | @dataclass 7 | class Event: 8 | type: str 9 | name: str 10 | id: int 11 | 12 | @staticmethod 13 | def from_json(raw_json: bytes): 14 | return jsons.load(json.loads(raw_json)["data"], Event) 15 | -------------------------------------------------------------------------------- /matcher/matcher/models/match_result.py: -------------------------------------------------------------------------------- 1 | from typing import List, Tuple 2 | from matcher.models.api.dto import ExternalMetadataDto 3 | from datetime import date 4 | from dataclasses import dataclass 5 | 6 | from matcher.providers.domain import AlbumType 7 | 8 | type SyncedLyrics = List[Tuple[float, str]] 9 | 10 | 11 | @dataclass 12 | class LyricsMatchResult: 13 | plain: str 14 | synced: SyncedLyrics | None 15 | 16 | 17 | @dataclass 18 | class SongMatchResult: 19 | metadata: ExternalMetadataDto | None 20 | lyrics: LyricsMatchResult | None 21 | genres: List[str] 22 | 23 | 24 | @dataclass 25 | class AlbumMatchResult: 26 | metadata: ExternalMetadataDto | None 27 | release_date: date | None 28 | album_type: AlbumType | None 29 | genres: List[str] 30 | 31 | 32 | @dataclass 33 | class ArtistMatchResult: 34 | metadata: ExternalMetadataDto | None 35 | illustration_url: str | None 36 | -------------------------------------------------------------------------------- /matcher/matcher/providers/base.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Type, TypeVarTuple, Callable 2 | from matcher.settings import BaseProviderSettings 3 | from ..models.api.provider import Provider as ApiProviderEntry 4 | from typing import List 5 | from dataclasses import dataclass, field 6 | 7 | Settings = TypeVar("Settings", bound=BaseProviderSettings, default=BaseProviderSettings) 8 | 9 | 10 | @dataclass 11 | class BaseFeature[*Args, Res]: 12 | Args = TypeVarTuple("Args") 13 | Res = TypeVar("Res") 14 | 15 | run: Callable[[*Args], Res] 16 | 17 | 18 | @dataclass 19 | class BaseProvider[Settings]: 20 | T = TypeVar("T") 21 | api_model: ApiProviderEntry 22 | settings: Settings 23 | features: List[BaseFeature] = field(init=False) 24 | 25 | def has_feature(self, t: Type[T]) -> bool: 26 | return any([f for f in self.features if isinstance(f, t)]) 27 | 28 | def get_feature(self, t: Type[T]) -> T | None: 29 | try: 30 | return [f for f in self.features if isinstance(f, t)][0] 31 | except Exception: 32 | pass 33 | -------------------------------------------------------------------------------- /matcher/matcher/providers/domain.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import TypeAlias 3 | from enum import Enum 4 | 5 | 6 | @dataclass 7 | class ArtistSearchResult: 8 | id: str 9 | 10 | 11 | @dataclass 12 | class AlbumSearchResult: 13 | id: str 14 | 15 | 16 | @dataclass 17 | class SongSearchResult: 18 | id: str 19 | 20 | 21 | ResourceUrl: TypeAlias = str 22 | 23 | ResourceName: TypeAlias = str 24 | 25 | ResourceId: TypeAlias = str 26 | 27 | class AlbumType(Enum): 28 | STUDIO = "StudioRecording" 29 | LIVE = "LiveRecording" 30 | REMIXES= "RemixAlbum" 31 | COMPILATION = "Compilation" 32 | SINGLE = "Single" 33 | SOUNDTRACK = "Soundtrack" 34 | VIDEO = "VideoAlbum" 35 | OTHER = "Other" 36 | -------------------------------------------------------------------------------- /matcher/matcher/providers/wikidata.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Any 3 | import requests 4 | 5 | 6 | @dataclass 7 | class WikidataRelations: 8 | _data: Any 9 | 10 | # Parameter is sth like P110000 11 | def get_identifier_from_provider_using_relation_key(self, relation_key: str): 12 | try: 13 | return self._data["statements"][relation_key][0]["value"]["content"] 14 | except Exception: 15 | return None 16 | 17 | 18 | # Note: Not a regular provider, we use it to link with other providers 19 | @dataclass 20 | class WikidataProvider: 21 | def get_resource_relations(self, wikidata_id): 22 | try: 23 | return WikidataRelations( 24 | requests.get( 25 | f"https://wikidata.org/w/rest.php/wikibase/v1/entities/items/{wikidata_id}" 26 | ).json() 27 | ) 28 | except Exception: 29 | return None 30 | -------------------------------------------------------------------------------- /matcher/matcher/utils.py: -------------------------------------------------------------------------------- 1 | from slugify import slugify 2 | 3 | 4 | def to_slug(s: str) -> str: 5 | return slugify("".join(s.lower().split())).replace("-", "") 6 | 7 | 8 | def capitalize_all_words(s: str) -> str: 9 | res = [] 10 | for word in s.split(): 11 | word = word.capitalize() 12 | for c in ["&", "-", "/"]: 13 | if c in word: 14 | word = c.join([capitalize_all_words(w) for w in word.split(c)]) 15 | res.append(word) 16 | return " ".join(res) 17 | -------------------------------------------------------------------------------- /matcher/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.pyright] 2 | ignore = ['tests/matcher'] 3 | -------------------------------------------------------------------------------- /matcher/requirements.txt: -------------------------------------------------------------------------------- 1 | pika 2 | dataclasses-json 3 | jsons 4 | requests 5 | unittest2 6 | musicbrainzngs 7 | python3-discogs-client 8 | python-dotenv 9 | beautifulsoup4 10 | python-slugify 11 | -------------------------------------------------------------------------------- /matcher/tests/__main__.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from .api import * 3 | from .settings import * 4 | from .matcher.artist import * 5 | from .matcher.album import * 6 | from .matcher.song import * 7 | from .providers.metacritic import * 8 | from .providers.allmusic import * 9 | from .providers.lrclib import * 10 | from .providers.wikipedia import * 11 | from .providers.musicbrainz import * 12 | from .providers.genius import * 13 | from .providers.discogs import * 14 | from dotenv import load_dotenv 15 | 16 | if __name__ == "__main__": 17 | load_dotenv() 18 | unittest.main() 19 | -------------------------------------------------------------------------------- /matcher/tests/api.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest import mock 3 | from matcher.api import API 4 | import os 5 | 6 | 7 | class TestAPI(unittest.TestCase): 8 | @mock.patch.dict( 9 | os.environ, {"API_URL": "localhost:4000", "API_KEYS": "abcd,efgh"}, clear=True 10 | ) 11 | def test_url_and_keys(self): 12 | api = API() 13 | self.assertEqual(api._url, "localhost:4000") 14 | self.assertEqual(api._key, "abcd") 15 | 16 | @mock.patch.dict(os.environ, {"API_KEYS": "abcd,efgh"}, clear=True) 17 | def test_missing_url(self): 18 | with self.assertRaises(Exception): 19 | API() 20 | 21 | @mock.patch.dict(os.environ, {"API_URL": "localhost:4000"}, clear=True) 22 | def test_missing_key(self): 23 | with self.assertRaises(Exception): 24 | API() 25 | -------------------------------------------------------------------------------- /matcher/tests/assets/bad_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "useExternalProviderGenres": false 4 | }, 5 | "providers": { 6 | "genius": { "apiKey": 1234 }, 7 | "musicbrainz": {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /matcher/tests/assets/missing_field.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "useExternalProviderGenres": false 4 | }, 5 | "providers": { 6 | "genius": {}, 7 | "musicbrainz": {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /matcher/tests/assets/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "trackRegex": [ 3 | "^([\\/\\\\]+.*)*[\\/\\\\]+(?.+)[\\/\\\\]+(?.+)(\\s+\\((?\\d{4})\\))[\\/\\\\]+((?[0-9]+)-)?(?[0-9]+)\\s+(?.*)\\s+\\((?.*)\\)\\..*$", 4 | "^([\\/\\\\]+.*)*[\\/\\\\]+(?.+)[\\/\\\\]+(?.+)(\\s+\\((?\\d{4})\\))[\\/\\\\]+((?[0-9]+)-)?(?[0-9]+)\\s+(?.*)\\..*$" 5 | ], 6 | "metadata": { 7 | "source": "embedded", 8 | "order": "only", 9 | "useExternalProviderGenres": true 10 | }, 11 | "providers": { 12 | "genius": { "apiKey": "azerty" }, 13 | "musicbrainz": {} 14 | }, 15 | "compilations": { 16 | "useID3CompTag": true, 17 | "artists": [] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /matcher/tests/providers/allmusic.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import datetime 3 | from matcher.context import Context 4 | from tests.matcher.common import MatcherTestUtils 5 | from matcher.providers.allmusic import AllMusicProvider 6 | 7 | 8 | class TestAllMusic(unittest.TestCase): 9 | @classmethod 10 | def setUpClass(cls): 11 | MatcherTestUtils.setup_context() 12 | 13 | def test_get_album_rating_and_release_date(self): 14 | provider: AllMusicProvider = Context().get().get_provider(AllMusicProvider) # pyright: ignore 15 | album = provider.get_album("mw0004378326") 16 | self.assertIsNotNone(album) 17 | rating = provider.get_album_rating(album) 18 | self.assertEqual(rating, 70) 19 | release_date = provider.get_album_release_date(album) 20 | self.assertEqual(release_date, datetime.date(2024, 10, 18)) 21 | 22 | def test_get_album_rating_when_null_and_release_date(self): 23 | provider: AllMusicProvider = Context().get().get_provider(AllMusicProvider) # pyright: ignore 24 | album = provider.get_album("mw0000770491") 25 | self.assertIsNotNone(album) 26 | rating = provider.get_album_rating(album) 27 | self.assertIsNone(rating) 28 | release_date = provider.get_album_release_date(album) 29 | self.assertEqual(release_date, datetime.date(2003, 3, 24)) 30 | -------------------------------------------------------------------------------- /matcher/tests/providers/metacritic.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import datetime 3 | from matcher.context import Context 4 | from tests.matcher.common import MatcherTestUtils 5 | from matcher.providers.metacritic import MetacriticProvider 6 | 7 | 8 | class TestMetacritic(unittest.TestCase): 9 | @classmethod 10 | def setUpClass(cls): 11 | MatcherTestUtils.setup_context() 12 | 13 | def test_get_album_rating_and_release_date(self): 14 | provider: MetacriticProvider = Context().get().get_provider(MetacriticProvider) # pyright: ignore 15 | album = provider.get_album("renaissance/beyonce") # pyright: ignore 16 | self.assertIsNotNone(album) 17 | rating = provider.get_album_rating(album) 18 | self.assertEqual(rating, 91) 19 | release_date = provider.get_album_release_date(album) 20 | self.assertEqual(release_date, datetime.date(2022, 7, 29)) 21 | -------------------------------------------------------------------------------- /nginx.conf.template: -------------------------------------------------------------------------------- 1 | server { 2 | listen ${PORT} default_server; 3 | listen [::]:${PORT} default_server; 4 | access_log off; 5 | server_name _; 6 | 7 | location = /api { 8 | return 302 /api/; 9 | } 10 | location /api/ { 11 | proxy_pass ${SERVER_URL}/; 12 | } 13 | location = /scanner { 14 | return 302 /scanner/; 15 | } 16 | location /scanner/ { 17 | proxy_pass ${SCANNER_URL}/; 18 | } 19 | location / { 20 | proxy_pass ${FRONT_URL}; 21 | } 22 | } -------------------------------------------------------------------------------- /scanner/.gitignore: -------------------------------------------------------------------------------- 1 | scanner 2 | coverage.out 3 | app/docs -------------------------------------------------------------------------------- /scanner/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24.1-alpine AS builder 2 | RUN go install github.com/swaggo/swag/cmd/swag@latest 3 | WORKDIR /app 4 | COPY go.mod go.sum ./ 5 | RUN go mod download 6 | COPY ./app ./app 7 | COPY ./internal ./internal 8 | RUN swag init -d app -o ./app/docs 9 | RUN GOOS=linux go build -o ./scanner ./app 10 | 11 | FROM golang:1.22.6-alpine AS runner 12 | 13 | ENV SERVICE_NAME="scanner" 14 | RUN adduser --disabled-password -s /bin/false $SERVICE_NAME 15 | 16 | RUN apk update && apk upgrade && apk add ffmpeg chromaprint mailcap 17 | WORKDIR /app 18 | COPY --from=builder /app/scanner ./ 19 | USER $SERVICE_NAME 20 | CMD ["./scanner"] 21 | -------------------------------------------------------------------------------- /scanner/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM golang:1.24.1-alpine 2 | RUN go install github.com/bokwoon95/wgo@latest 3 | RUN go install github.com/swaggo/swag/cmd/swag@latest 4 | 5 | RUN apk update && apk upgrade && apk add ffmpeg chromaprint mailcap 6 | WORKDIR /app 7 | 8 | CMD ["wgo", "-xdir", "./app/docs", "swag", "init", "-d", "app", "-o", "./app/docs", "::", "go", "run", "./app"] 9 | -------------------------------------------------------------------------------- /scanner/README.md: -------------------------------------------------------------------------------- 1 | # Meelo's Scanner 2 | 3 | ## Configuration 4 | 5 | ### Environment Variables 6 | 7 | - `API_URL`: URL to the API 8 | - `INTERNAL_CONFIG_DIR`: Path of the directory that contains the `settings.json` file 9 | - `INTERNAL_DATA_DIR`: Path of the directory where all the libraries are. 10 | - `API_KEY`: Key used to authenticate to the API. 11 | - Or if `API_KEYS` exists and is a coma-separated string, we will take the first strings before the first `,`. 12 | 13 | ### Files 14 | 15 | - `settings.json`: JSON File located in `INTERNAL_CONFIG_DIR`. See user doc for specs 16 | -------------------------------------------------------------------------------- /scanner/app/context.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/Arthi-chaud/Meelo/scanner/internal/config" 5 | t "github.com/Arthi-chaud/Meelo/scanner/internal/tasks" 6 | ) 7 | 8 | type ScannerContext struct { 9 | config *config.Config 10 | worker *t.Worker 11 | } 12 | -------------------------------------------------------------------------------- /scanner/internal/api/models.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type User struct { 4 | Admin bool `validate:"required" json:"admin"` 5 | } 6 | 7 | type Page[T any] struct { 8 | Items []T `validate:"required,dive,required" json:"items"` 9 | Metadata PageMetadata `json:"metadata"` 10 | } 11 | 12 | type PageMetadata struct { 13 | Next string `json:"next"` 14 | Count uint64 `json:"count"` 15 | } 16 | 17 | type Library struct { 18 | Id int `json:"id" validate:"required"` 19 | Name string `json:"name" validate:"required"` 20 | Slug string `json:"slug" validate:"required"` 21 | Path string `json:"path" validate:"required"` 22 | } 23 | 24 | type File struct { 25 | Id int `json:"id" validate:"required"` 26 | Path string `json:"path" validate:"required"` 27 | Checksum string `json:"checksum" validate:"required"` 28 | LibraryId int `json:"libraryId" validate:"required"` 29 | } 30 | 31 | type MetadataCreated struct { 32 | TrackId int `json:"trackId" validate:"required"` 33 | SongId int `json:"songId"` 34 | } 35 | 36 | // Do not change names of fields, they are mapped 1:1 with the query parameters of the requests 37 | type FileSelectorDto struct { 38 | Library string 39 | Album string 40 | Release string 41 | Song string 42 | Track string 43 | } 44 | 45 | type IllustrationType string 46 | 47 | const ( 48 | Cover IllustrationType = "Cover" 49 | Thumbnail IllustrationType = "Thumbnail" 50 | ) 51 | -------------------------------------------------------------------------------- /scanner/internal/checksum.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "crypto/sha256" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func ComputeChecksum(filepath string) (string, error) { 10 | stat, err := os.Stat(filepath) 11 | if err != nil { 12 | return "", err 13 | } 14 | fileDate := stat.ModTime() 15 | fileSize := stat.Size() 16 | hashSource := fmt.Sprintf("%s-%s-%d", filepath, fileDate.Format("2006-01-02 15:04:05"), fileSize) 17 | 18 | // SRC: https://gobyexample.com/sha256-hashes 19 | h := sha256.New() 20 | h.Write([]byte(hashSource)) 21 | checksum := h.Sum(nil) 22 | 23 | return fmt.Sprintf("%x", checksum), nil 24 | } 25 | -------------------------------------------------------------------------------- /scanner/internal/constants.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | // !! Changing this value is a breaking change !! 4 | const CompilationKeyword = "compilations" 5 | -------------------------------------------------------------------------------- /scanner/internal/filesystem/filesystem.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | import ( 4 | "os" 5 | "path" 6 | ) 7 | 8 | func GetAllFilesInDirectory(dir string) ([]string, error) { 9 | files := []string{} 10 | entries, err := os.ReadDir(dir) 11 | 12 | if err != nil { 13 | return []string{}, err 14 | } 15 | 16 | for _, e := range entries { 17 | entryPath := path.Join(dir, e.Name()) 18 | i, err := os.Stat(entryPath) 19 | if err != nil { 20 | return []string{}, err 21 | } 22 | if i.IsDir() { 23 | filesInDir, err := GetAllFilesInDirectory(entryPath) 24 | if err != nil { 25 | return []string{}, err 26 | } 27 | files = append(files, filesInDir...) 28 | } else { 29 | files = append(files, entryPath) 30 | } 31 | } 32 | return files, nil 33 | } 34 | -------------------------------------------------------------------------------- /scanner/internal/fingerprint.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "encoding/json" 5 | "os/exec" 6 | ) 7 | 8 | type Fingerprint struct { 9 | Fingerprint string `json:"fingerprint"` 10 | } 11 | 12 | func GetFileAcousticFingerprint(filepath string) (string, error) { 13 | cmd := exec.Command("fpcalc", filepath, "-json", "-algorithm", "2", "-overlap", "-channels", "2") 14 | output, err := cmd.Output() 15 | if err != nil { 16 | return "", err 17 | } 18 | res := Fingerprint{} 19 | err = json.Unmarshal([]byte(output), &res) 20 | if err != nil { 21 | return "", err 22 | } 23 | return res.Fingerprint, nil 24 | } 25 | -------------------------------------------------------------------------------- /scanner/internal/illustration/embedded.go: -------------------------------------------------------------------------------- 1 | package illustration 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ffmpeg_go "github.com/u2takey/ffmpeg-go" 7 | "gopkg.in/vansante/go-ffprobe.v2" 8 | ) 9 | 10 | func GetEmbeddedIllustrationStreamIndex(probeData ffprobe.ProbeData) int { 11 | for _, stream := range probeData.Streams { 12 | if stream != nil && stream.Disposition.AttachedPic == 1 { 13 | return stream.Index 14 | } 15 | } 16 | return -1 17 | } 18 | 19 | func ExtractEmbeddedIllustration(filePath string, illustrationStreamIndex int) ([]byte, error) { 20 | buf := bytes.NewBuffer(nil) 21 | err := ffmpeg_go.Input(filePath). 22 | Silent(true). 23 | Get(fmt.Sprintf("%d", illustrationStreamIndex)). 24 | Output("pipe:", ffmpeg_go.KwArgs{"vcodec": "mjpeg", "format": "image2"}). 25 | WithOutput(buf).Run() 26 | return buf.Bytes(), err 27 | } 28 | -------------------------------------------------------------------------------- /scanner/internal/illustration/inline.go: -------------------------------------------------------------------------------- 1 | package illustration 2 | 3 | import ( 4 | "os" 5 | "path" 6 | "regexp" 7 | ) 8 | 9 | const IllustrationNameRegex = "(cover|artwork)\\..*$" 10 | 11 | func GetIllustrationFilePath(trackPath string) string { 12 | parentDir := path.Dir(trackPath) 13 | entries, err := os.ReadDir(parentDir) 14 | if err != nil { 15 | return "" 16 | } 17 | regex := regexp.MustCompile(IllustrationNameRegex) 18 | for _, file := range entries { 19 | if file.IsDir() { 20 | continue 21 | } 22 | matches := regex.FindStringSubmatch(file.Name()) 23 | if len(matches) > 0 { 24 | fullFilePath := path.Join(parentDir, file.Name()) 25 | return fullFilePath 26 | } 27 | } 28 | return "" 29 | } 30 | -------------------------------------------------------------------------------- /scanner/internal/illustration/inline_test.go: -------------------------------------------------------------------------------- 1 | package illustration 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "path" 6 | "testing" 7 | ) 8 | 9 | func TestGetIllustrationPath(t *testing.T) { 10 | mediaPath := path.Join("..", "..", "testdata", "dreams.m4a") 11 | illustrationPath := GetIllustrationFilePath(mediaPath) 12 | 13 | assert.Equal(t, "../../testdata/cover.jpg", illustrationPath) 14 | } 15 | -------------------------------------------------------------------------------- /scanner/internal/lyrics_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "os" 5 | "path" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestLyricsParsing(t *testing.T) { 12 | 13 | path := path.Join("..", "testdata", "lyrics.lrc") 14 | lyric, _ := os.ReadFile(path) 15 | res := ParseLyrics(string(lyric)) 16 | switch lyrics := res.(type) { 17 | case PlainLyrics: 18 | assert.Fail(t, "Returned Plainlyrics instead of SyncedLyrics") 19 | case SyncedLyrics: 20 | assert.Equal(t, 5, len(lyrics)) 21 | expected := []SyncedLyric{ 22 | {Timestamp: 2, Content: "Line 1 lyrics"}, 23 | {Timestamp: 3.20, Content: "Line 2 lyrics"}, 24 | {Timestamp: 46.10, Content: "Repeating lyrics"}, 25 | {Timestamp: 64.10, Content: "Repeating lyrics"}, 26 | {Timestamp: 65.00, Content: "Last lyrics line"}, 27 | } 28 | // For easier log/debug 29 | for i, expectedEntry := range expected { 30 | assert.Equal(t, expectedEntry, lyrics[i]) 31 | } 32 | return 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /scanner/internal/metadata_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestVMergeMetadataCorrectOverride(t *testing.T) { 11 | m1 := Metadata{ 12 | Artist: "A", 13 | AlbumArtist: "", 14 | ReleaseDate: nil, 15 | } 16 | m2Date := time.Date(2007, 1, 1, 1, 1, 1, 1, time.UTC) 17 | m2 := Metadata{ 18 | Artist: "B", 19 | AlbumArtist: "B", 20 | ReleaseDate: &m2Date, 21 | } 22 | m3, _ := Merge(m1, m2) 23 | 24 | assert.Equal(t, "A", m3.Artist) 25 | assert.Equal(t, "B", m3.AlbumArtist) 26 | assert.Equal(t, 2007, m3.ReleaseDate.Year()) 27 | } 28 | -------------------------------------------------------------------------------- /scanner/internal/tasks/task.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | import ( 4 | "github.com/Arthi-chaud/Meelo/scanner/internal" 5 | "github.com/google/uuid" 6 | ) 7 | 8 | type Task struct { 9 | Id string 10 | Name string 11 | Exec func(w *Worker) error 12 | } 13 | 14 | type TaskInfo struct { 15 | Id string 16 | Name string 17 | } 18 | 19 | func (t Task) GetInfo() TaskInfo { 20 | return TaskInfo{Id: t.Id, Name: t.Name} 21 | } 22 | 23 | func createTask(name string, exec func(w *Worker) error) Task { 24 | return Task{ 25 | Id: uuid.New().String(), 26 | Name: name, 27 | Exec: exec, 28 | } 29 | } 30 | 31 | type ThumbnailTask struct { 32 | TrackId int 33 | TrackDuration int 34 | FilePath string 35 | } 36 | 37 | type IllustrationTask struct { 38 | IllustrationLocation internal.IllustrationLocation 39 | IllustrationPath string 40 | TrackPath string 41 | TrackId int 42 | IllustrationStreamIndex int 43 | } 44 | -------------------------------------------------------------------------------- /scanner/internal/utils.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "unicode" 4 | 5 | func IsNumeric(s string) bool { 6 | for _, c := range s { 7 | if !unicode.IsDigit(c) { 8 | return false 9 | } 10 | } 11 | return true 12 | } 13 | 14 | // Applies `f` to each element of ts 15 | func Fmap[T, U any](ts []T, f func(T, int) U) []U { 16 | us := make([]U, len(ts)) 17 | for i := range ts { 18 | us[i] = f(ts[i], i) 19 | } 20 | return us 21 | } 22 | 23 | func Contains[T comparable](s []T, e T) bool { 24 | for _, a := range s { 25 | if a == e { 26 | return true 27 | } 28 | } 29 | return false 30 | } 31 | 32 | func Filter[T any](ss []T, test func(T) bool) (ret []T) { 33 | for _, s := range ss { 34 | if test(s) { 35 | ret = append(ret, s) 36 | } 37 | } 38 | return 39 | } 40 | -------------------------------------------------------------------------------- /scanner/internal/validation.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-playground/validator/v10" 7 | ) 8 | 9 | // Parameters should be the returned error from validator.New().Struct(..) 10 | // prefix will be added at the beginning of every returned errors 11 | func PrettifyValidationError(validationsErrs error, prefix string) []error { 12 | var errors []error 13 | if validationsErrs != nil { 14 | for _, validationErr := range validationsErrs.(validator.ValidationErrors) { 15 | var err = fmt.Errorf( 16 | "%s: validation failed for '%s'. constraint: %s(%s), Got '%s'", 17 | prefix, 18 | validationErr.StructNamespace(), validationErr.Tag(), 19 | validationErr.Param(), validationErr.Value(), 20 | ) 21 | errors = append(errors, err) 22 | } 23 | } 24 | return errors 25 | } 26 | -------------------------------------------------------------------------------- /scanner/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=arthi-chaud_Meelo-scanner 2 | sonar.organization=arthi-chaud 3 | 4 | sonar.projectName=Meelo (Scanner) 5 | 6 | 7 | sonar.sources=./app,./internal/ 8 | sonar.exclusions=**/*_test.go 9 | 10 | sonar.tests=./internal/ 11 | sonar.test.inclusions=**/*_test.go 12 | sonar.go.coverage.reportPaths=./coverage.out -------------------------------------------------------------------------------- /scanner/testdata/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/scanner/testdata/cover.jpg -------------------------------------------------------------------------------- /scanner/testdata/dreams.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/scanner/testdata/dreams.m4a -------------------------------------------------------------------------------- /scanner/testdata/lyrics.lrc: -------------------------------------------------------------------------------- 1 | [00:01.00]Line 1 lyrics 2 | [offset:-1000] 3 | [00:02.20]Line 2 lyrics 4 | 5 | [01:03.10][00:45.10]Repeating lyrics 6 | [01:04.00] Last lyrics line 7 | 8 | [#:Taken from Wikipedia Example] 9 | -------------------------------------------------------------------------------- /scanner/testdata/test.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/scanner/testdata/test.flac -------------------------------------------------------------------------------- /scanner/testdata/test.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/scanner/testdata/test.opus -------------------------------------------------------------------------------- /scanner/testdata/user_settings/settings-empty-file.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/scanner/testdata/user_settings/settings-empty-file.json -------------------------------------------------------------------------------- /scanner/testdata/user_settings/settings-empty-regex.json: -------------------------------------------------------------------------------- 1 | { 2 | "trackRegex": [], 3 | "metadata": { 4 | "source": "embedded", 5 | "order": "only" 6 | } 7 | } -------------------------------------------------------------------------------- /scanner/testdata/user_settings/settings-invalid.json: -------------------------------------------------------------------------------- 1 | { 2 | "trackRegex": [ 3 | "regex1", 4 | 5 | } -------------------------------------------------------------------------------- /scanner/testdata/user_settings/settings-missing-metadata-order.json: -------------------------------------------------------------------------------- 1 | { 2 | "trackRegex": [ 3 | "^([\\/\\\\]+.*)*[\\/\\\\]*(?P.+)[\\/\\\\]+(?P.+)[\\/\\\\]+((?P[0-9]+)-)?(?P[0-9]+)\\s+(?P.*)\\..*$", 4 | "^([\\/\\\\]+.*)*[\\/\\\\]*(?P.+)[\\/\\\\]+(?P.+)(\\s*\\((?P\\d{4})\\))[\\/\\\\]+((?P[0-9]+)-)?(?P[0-9]+)\\s+(?P.*)\\..*$" 5 | ], 6 | "metadata": { 7 | "source": "embedded" 8 | }, 9 | "compilations": { 10 | "useID3CompTag": true 11 | } 12 | } -------------------------------------------------------------------------------- /scanner/testdata/user_settings/settings-missing-metadata-source.json: -------------------------------------------------------------------------------- 1 | { 2 | "trackRegex": [ 3 | "^([\\/\\\\]+.*)*[\\/\\\\]*(?P.+)[\\/\\\\]+(?P.+)[\\/\\\\]+((?P[0-9]+)-)?(?P[0-9]+)\\s+(?P.*)\\..*$", 4 | "^([\\/\\\\]+.*)*[\\/\\\\]*(?P.+)[\\/\\\\]+(?P.+)(\\s*\\((?P\\d{4})\\))[\\/\\\\]+((?P[0-9]+)-)?(?P[0-9]+)\\s+(?P.*)\\..*$" 5 | ], 6 | "metadata": { 7 | "order": "only" 8 | }, 9 | "compilations": { 10 | "useID3CompTag": true 11 | } 12 | } -------------------------------------------------------------------------------- /scanner/testdata/user_settings/settings-missing-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "trackRegex": [ 3 | "^([\\/\\\\]+.*)*[\\/\\\\]*(?P.+)[\\/\\\\]+(?P.+)[\\/\\\\]+((?P[0-9]+)-)?(?P[0-9]+)\\s+(?P.*)\\..*$", 4 | "^([\\/\\\\]+.*)*[\\/\\\\]*(?P.+)[\\/\\\\]+(?P.+)(\\s*\\((?P\\d{4})\\))[\\/\\\\]+((?P[0-9]+)-)?(?P[0-9]+)\\s+(?P.*)\\..*$" 5 | ], 6 | "compilations": { 7 | "useID3CompTag": true 8 | } 9 | } -------------------------------------------------------------------------------- /scanner/testdata/user_settings/settings-missing-regex.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "source": "embedded", 4 | "order": "only" 5 | }, 6 | "compilations": { 7 | "useID3CompTag": true 8 | } 9 | } -------------------------------------------------------------------------------- /scanner/testdata/user_settings/settings-wrong-type-metadata-source.json: -------------------------------------------------------------------------------- 1 | { 2 | "trackRegex": [ 3 | "regex1" 4 | ], 5 | "metadata": { 6 | "source": "lastfm", 7 | "order": "only", 8 | "useExternalProviderGenres": false 9 | }, 10 | "compilations": { 11 | "useID3CompTag": true 12 | } 13 | } -------------------------------------------------------------------------------- /scanner/testdata/user_settings/settings-wrong-type.json: -------------------------------------------------------------------------------- 1 | { 2 | "trackRegex": [ 3 | "regex1" 4 | ], 5 | "compilations": { 6 | "useID3CompTag": true 7 | }, 8 | "metadata": true 9 | } -------------------------------------------------------------------------------- /scanner/testdata/user_settings/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "trackRegex": [ 3 | "^([\\/\\\\]+.*)*[\\/\\\\]+(?P.+)[\\/\\\\]+(?P.+)(\\s+\\((?P\\d{4})\\))[\\/\\\\]+((?P[0-9]+)-)?(?P[0-9]+)\\s+(?P.*)\\s+\\((?P.*)\\)\\..*$", 4 | "^([\\/\\\\]+.*)*[\\/\\\\]+(?P.+)[\\/\\\\]+(?P.+)(\\s+\\((?P\\d{4})\\))[\\/\\\\]+((?P[0-9]+)-)?(?P[0-9]+)\\s+(?P.*)\\..*$" 5 | ], 6 | "metadata": { 7 | "source": "embedded", 8 | "order": "only", 9 | "useExternalProviderGenres": false 10 | }, 11 | "compilations": { 12 | "useID3CompTag": true, 13 | "artists": [] 14 | } 15 | } -------------------------------------------------------------------------------- /scanner/testdata/user_settings/settings2.json: -------------------------------------------------------------------------------- 1 | { 2 | "trackRegex": [ 3 | "^([\\/\\\\]+.*)*[\\/\\\\]*(?P.+)[\\/\\\\]+(?P.+)(\\s*\\((?P\\d{4})\\))[\\/\\\\]+((?P[0-9]+)-)?(?P[0-9]+)\\s+(?P.*)\\..*$" 4 | ], 5 | "metadata": { 6 | "source": "path", 7 | "order": "preferred", 8 | "useExternalProviderGenres": true 9 | }, 10 | "providers": { 11 | 12 | }, 13 | "compilations": { 14 | "useID3CompTag": true 15 | } 16 | } -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | dist/ 3 | node_modules/ 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Tests 15 | coverage 16 | .nyc_output 17 | 18 | 19 | # Envrionment/Secret files 20 | .env 21 | illustrations/ 22 | data/ 23 | meelo/ 24 | 25 | coverage/ 26 | test/assets/metadata 27 | 28 | src/prisma/models 29 | .yarn/* 30 | !.yarn/patches 31 | !.yarn/plugins 32 | !.yarn/releases 33 | !.yarn/sdks 34 | -------------------------------------------------------------------------------- /server/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 4, 4 | "useTabs": true, 5 | "semi": true, 6 | "singleQuote": false, 7 | "quoteProps": "as-needed", 8 | "jsxSingleQuote": false, 9 | "trailingComma": "all", 10 | "bracketSpacing": true, 11 | "bracketSameLine": false, 12 | "arrowParens": "always", 13 | "endOfLine": "lf", 14 | "singleAttributePerLine": false 15 | } 16 | -------------------------------------------------------------------------------- /server/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.4.0.cjs 4 | -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-alpine3.20 AS base 2 | RUN apk update && apk add openssl 3 | 4 | FROM base AS builder 5 | WORKDIR /app 6 | # Source: https://stackoverflow.com/questions/67903114/javascript-heap-out-of-memory-in-docker-image-run 7 | ENV NODE_OPTIONS=--max-old-space-size=16384 8 | COPY ./*.json ./.yarnrc.yml ./*.lock ./ 9 | COPY ./.yarn ./.yarn 10 | RUN corepack enable 11 | RUN yarn workspaces focus 12 | COPY ./src ./src 13 | COPY ./prisma/ ./prisma 14 | RUN yarn install --immutable 15 | RUN yarn run build && yarn workspaces focus --all --production 16 | 17 | FROM base as runner 18 | WORKDIR /app 19 | COPY --from=builder /app/node_modules ./node_modules 20 | COPY --from=builder /app/package.json /app/yarn.lock /app/.yarnrc.yml ./ 21 | COPY --from=builder /app/.yarn ./.yarn 22 | COPY --from=builder /app/prisma ./prisma 23 | COPY --from=builder /app/dist ./dist 24 | RUN corepack enable 25 | 26 | CMD yarn run prisma migrate dev --skip-generate && yarn run start:prod 27 | 28 | -------------------------------------------------------------------------------- /server/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM node:22-alpine3.20 AS base 2 | RUN apk update && apk add openssl 3 | 4 | FROM base AS builder 5 | WORKDIR /app 6 | COPY ./*.json ./ 7 | COPY ./*.lock ./ 8 | COPY .yarn ./.yarn 9 | COPY .yarnrc.yml ./ 10 | RUN corepack enable 11 | RUN yarn 12 | COPY ./prisma ./prisma 13 | RUN yarn build 14 | 15 | CMD yarn \ 16 | && yarn build \ 17 | && yarn run prisma migrate dev \ 18 | && yarn start:dev 19 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # Meelo's Server 2 | 3 | ## Configuration 4 | 5 | ### Environment Variables 6 | 7 | - `MEILI_HOST`: Hostname of the service with a Meilisearch instance 8 | - `MEILI_MASTER_KEY`: Master Key used to authenticate to Meilisearch 9 | - `RABBITMQ_URL`: URL of the RabbitMQ instance 10 | - Example: `amqp://rabbitmq:rabbitmq@localhost:5672` 11 | - `TRANSCODER_URL`: URL of the Transcoder service 12 | - Example: `http://transcoder:7666` 13 | - `API_KEYS`: List of comma separated keys used by microservices (e.g. scanner) to authenticate 14 | - `JWT_SIGNATURE`: Random String used to sign JWT Tokens 15 | - `ALLOW_ANONYMOUS`: If `1`, anonymous requests will be allowed on read-only endpoints 16 | - `INTERNAL_CONFIG_DIR`: Path of the directory that contains the `settings.json` file 17 | - `INTERNAL_DATA_DIR`: Path of the directory where all the libraries are. 18 | - `LASTFM_API_KEY`& `LASTFM_API_SECRET`: Key and secret obtained when creating an API account on LastFM 19 | 20 | ### Files 21 | 22 | - `settings.json`: JSON File located in `INTERNAL_CONFIG_DIR`. See user doc for specs 23 | -------------------------------------------------------------------------------- /server/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../biome.json"], 3 | "formatter": { 4 | "lineWidth": 80, 5 | "ignore": ["src/prisma/models/**"] 6 | }, 7 | "linter": { 8 | "rules": { 9 | "style": { 10 | "useEnumInitializers": "off", 11 | "noNonNullAssertion": "off", 12 | "useImportType": "off" 13 | }, 14 | "nursery": { 15 | "noRestrictedImports": { 16 | "options": { 17 | "paths": { 18 | "fluent-ffmpeg": "Use Ffmpeg Service", 19 | "fs": "Use FileManager Service", 20 | "node:fs": "Use FileManager Service", 21 | "src/pagination/paginated-response.decorator": "Do not use the decorator directly. Use 'Response' decorator from 'src/response/response.decorator.ts'", 22 | "src/pagination/models/paginated-response": "Do not use the class directly. Use 'Response' decorator from 'src/response/response.decorator.ts'" 23 | } 24 | } 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /server/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "watchAssets": true, 7 | "assets": ["public/*", "public/**/*"], 8 | "plugins": [ 9 | { 10 | "name": "@nestjs/swagger", 11 | "options": { 12 | "dtoFileNameSuffix": [".dto.ts", ".entity.ts"] 13 | } 14 | } 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /server/prisma/migrations/20221026083004_new_album_types/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | -- This migration adds more than one value to an enum. 3 | -- With PostgreSQL versions 11 and earlier, this is not possible 4 | -- in a single migration. This can be worked around by creating 5 | -- multiple migrations, each migration adding only one value to 6 | -- the enum. 7 | 8 | 9 | ALTER TYPE "album-types" ADD VALUE 'Soundtrack'; 10 | ALTER TYPE "album-types" ADD VALUE 'RemixAlbum'; 11 | ALTER TYPE "album-types" ADD VALUE 'VideoAlbum'; 12 | -------------------------------------------------------------------------------- /server/prisma/migrations/20221106165421_user_table/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "users" ( 3 | "id" SERIAL NOT NULL, 4 | "name" TEXT NOT NULL, 5 | "password" TEXT NOT NULL, 6 | "enabled" BOOLEAN NOT NULL DEFAULT false, 7 | "admin" BOOLEAN NOT NULL DEFAULT false, 8 | 9 | CONSTRAINT "users_pkey" PRIMARY KEY ("id") 10 | ); 11 | 12 | -- CreateIndex 13 | CREATE UNIQUE INDEX "users_name_key" ON "users"("name"); 14 | -------------------------------------------------------------------------------- /server/prisma/migrations/20230107210819_album_master/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `master` on the `releases` table. All the data in the column will be lost. 5 | - A unique constraint covering the columns `[masterId]` on the table `albums` will be added. If there are existing duplicate values, this will fail. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "albums" ADD COLUMN "masterId" INTEGER; 10 | 11 | -- AlterTable 12 | ALTER TABLE "releases" DROP COLUMN "master"; 13 | 14 | -- CreateIndex 15 | CREATE UNIQUE INDEX "albums_masterId_key" ON "albums"("masterId"); 16 | 17 | -- AddForeignKey 18 | ALTER TABLE "albums" ADD CONSTRAINT "albums_masterId_fkey" FOREIGN KEY ("masterId") REFERENCES "releases"("id") ON DELETE SET NULL ON UPDATE CASCADE; 19 | -------------------------------------------------------------------------------- /server/prisma/migrations/20230108090513_song_master/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `master` on the `tracks` table. All the data in the column will be lost. 5 | - A unique constraint covering the columns `[masterId]` on the table `songs` will be added. If there are existing duplicate values, this will fail. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "songs" ADD COLUMN "masterId" INTEGER; 10 | 11 | -- AlterTable 12 | ALTER TABLE "tracks" DROP COLUMN "master"; 13 | 14 | -- CreateIndex 15 | CREATE UNIQUE INDEX "songs_masterId_key" ON "songs"("masterId"); 16 | 17 | -- AddForeignKey 18 | ALTER TABLE "songs" ADD CONSTRAINT "songs_masterId_fkey" FOREIGN KEY ("masterId") REFERENCES "tracks"("id") ON DELETE SET NULL ON UPDATE CASCADE; 19 | -------------------------------------------------------------------------------- /server/prisma/migrations/20230113113317_lyrics_cascade_delete/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "lyrics" DROP CONSTRAINT "lyrics_songId_fkey"; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "lyrics" ADD CONSTRAINT "lyrics_songId_fkey" FOREIGN KEY ("songId") REFERENCES "songs"("id") ON DELETE CASCADE ON UPDATE CASCADE; 6 | -------------------------------------------------------------------------------- /server/prisma/migrations/20230207092742_registration_dates/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "albums" ADD COLUMN "registeredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; 3 | 4 | -- AlterTable 5 | ALTER TABLE "artists" ADD COLUMN "registeredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; 6 | 7 | -- AlterTable 8 | ALTER TABLE "releases" ADD COLUMN "registeredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; 9 | 10 | -- AlterTable 11 | ALTER TABLE "songs" ADD COLUMN "registeredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; 12 | -------------------------------------------------------------------------------- /server/prisma/migrations/20230414174453_playlists/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "playlists" ( 3 | "id" SERIAL NOT NULL, 4 | "name" CITEXT NOT NULL, 5 | "slug" TEXT NOT NULL, 6 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 7 | 8 | CONSTRAINT "playlists_pkey" PRIMARY KEY ("id") 9 | ); 10 | 11 | -- CreateTable 12 | CREATE TABLE "playlist_entries" ( 13 | "id" SERIAL NOT NULL, 14 | "songId" INTEGER NOT NULL, 15 | "playlistId" INTEGER NOT NULL, 16 | "index" INTEGER NOT NULL, 17 | 18 | CONSTRAINT "playlist_entries_pkey" PRIMARY KEY ("id") 19 | ); 20 | 21 | -- CreateIndex 22 | CREATE UNIQUE INDEX "playlists_slug_key" ON "playlists"("slug"); 23 | 24 | -- AddForeignKey 25 | ALTER TABLE "playlist_entries" ADD CONSTRAINT "playlist_entries_songId_fkey" FOREIGN KEY ("songId") REFERENCES "songs"("id") ON DELETE CASCADE ON UPDATE CASCADE; 26 | 27 | -- AddForeignKey 28 | ALTER TABLE "playlist_entries" ADD CONSTRAINT "playlist_entries_playlistId_fkey" FOREIGN KEY ("playlistId") REFERENCES "playlists"("id") ON DELETE CASCADE ON UPDATE CASCADE; 29 | -------------------------------------------------------------------------------- /server/prisma/migrations/20230727174917_song_types/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "song-types" AS ENUM ('Original', 'Live', 'Acoustic', 'Remix', 'Instrumental', 'Edit', 'Clean', 'Demo', 'Unknown'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "songs" ADD COLUMN "type" "song-types" NOT NULL DEFAULT 'Unknown'; 6 | -------------------------------------------------------------------------------- /server/prisma/migrations/20230914151519_acappela_song_type/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "song-types" ADD VALUE 'Acapella'; 3 | -------------------------------------------------------------------------------- /server/prisma/migrations/20230918121029_release_external_ids/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "release_external_ids" ( 3 | "id" SERIAL NOT NULL, 4 | "releaseId" INTEGER NOT NULL, 5 | "providerId" INTEGER NOT NULL, 6 | "value" TEXT NOT NULL, 7 | 8 | CONSTRAINT "release_external_ids_pkey" PRIMARY KEY ("id") 9 | ); 10 | 11 | -- AddForeignKey 12 | ALTER TABLE "release_external_ids" ADD CONSTRAINT "release_external_ids_releaseId_fkey" FOREIGN KEY ("releaseId") REFERENCES "releases"("id") ON DELETE CASCADE ON UPDATE CASCADE; 13 | 14 | -- AddForeignKey 15 | ALTER TABLE "release_external_ids" ADD CONSTRAINT "release_external_ids_providerId_fkey" FOREIGN KEY ("providerId") REFERENCES "providers"("id") ON DELETE CASCADE ON UPDATE CASCADE; 16 | -------------------------------------------------------------------------------- /server/prisma/migrations/20231028165358_secondary_song_artists/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "_ArtistToSong" ( 3 | "A" INTEGER NOT NULL, 4 | "B" INTEGER NOT NULL 5 | ); 6 | 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "_ArtistToSong_AB_unique" ON "_ArtistToSong"("A", "B"); 9 | 10 | -- CreateIndex 11 | CREATE INDEX "_ArtistToSong_B_index" ON "_ArtistToSong"("B"); 12 | 13 | -- AddForeignKey 14 | ALTER TABLE "_ArtistToSong" ADD CONSTRAINT "_ArtistToSong_A_fkey" FOREIGN KEY ("A") REFERENCES "artists"("id") ON DELETE CASCADE ON UPDATE CASCADE; 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "_ArtistToSong" ADD CONSTRAINT "_ArtistToSong_B_fkey" FOREIGN KEY ("B") REFERENCES "songs"("id") ON DELETE CASCADE ON UPDATE CASCADE; 18 | -------------------------------------------------------------------------------- /server/prisma/migrations/20231104152731_external_ids_description/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "album_external_ids" ADD COLUMN "description" TEXT; 3 | 4 | -- AlterTable 5 | ALTER TABLE "artist_external_ids" ADD COLUMN "description" TEXT; 6 | 7 | -- AlterTable 8 | ALTER TABLE "release_external_ids" ADD COLUMN "description" TEXT; 9 | 10 | -- AlterTable 11 | ALTER TABLE "song_external_ids" ADD COLUMN "description" TEXT; 12 | -------------------------------------------------------------------------------- /server/prisma/migrations/20231118192451_album_external_id_rating/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "album_external_ids" ADD COLUMN "rating" INTEGER; 3 | -------------------------------------------------------------------------------- /server/prisma/migrations/20231202193154_album_genres/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "_AlbumToGenre" ( 3 | "A" INTEGER NOT NULL, 4 | "B" INTEGER NOT NULL 5 | ); 6 | 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "_AlbumToGenre_AB_unique" ON "_AlbumToGenre"("A", "B"); 9 | 10 | -- CreateIndex 11 | CREATE INDEX "_AlbumToGenre_B_index" ON "_AlbumToGenre"("B"); 12 | 13 | -- AddForeignKey 14 | ALTER TABLE "_AlbumToGenre" ADD CONSTRAINT "_AlbumToGenre_A_fkey" FOREIGN KEY ("A") REFERENCES "albums"("id") ON DELETE CASCADE ON UPDATE CASCADE; 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "_AlbumToGenre" ADD CONSTRAINT "_AlbumToGenre_B_fkey" FOREIGN KEY ("B") REFERENCES "genres"("id") ON DELETE CASCADE ON UPDATE CASCADE; 18 | -------------------------------------------------------------------------------- /server/prisma/migrations/20231207184811_bonus_track_field/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "tracks" ADD COLUMN "isBonus" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /server/prisma/migrations/20231208190658_non_music_song_type/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "song-types" ADD VALUE 'NonMusic'; 3 | -------------------------------------------------------------------------------- /server/prisma/migrations/20231221132029_optional_bitrate_and_duration/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "tracks" ALTER COLUMN "bitrate" DROP NOT NULL, 3 | ALTER COLUMN "duration" DROP NOT NULL; 4 | -------------------------------------------------------------------------------- /server/prisma/migrations/20240103140210_song_groups/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `groupId` to the `songs` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "songs" ADD COLUMN "groupId" INTEGER NOT NULL; 9 | 10 | -- CreateTable 11 | CREATE TABLE "song_groups" ( 12 | "id" SERIAL NOT NULL, 13 | "slug" TEXT NOT NULL, 14 | 15 | CONSTRAINT "song_groups_pkey" PRIMARY KEY ("id") 16 | ); 17 | 18 | -- CreateIndex 19 | CREATE UNIQUE INDEX "song_groups_slug_key" ON "song_groups"("slug"); 20 | 21 | -- AddForeignKey 22 | ALTER TABLE "songs" ADD CONSTRAINT "songs_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "song_groups"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 23 | -------------------------------------------------------------------------------- /server/prisma/migrations/20240110124438_illustration_aspect_ratio/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "artist_illustrations" ADD COLUMN "aspectRatio" DOUBLE PRECISION NOT NULL DEFAULT 1; 3 | 4 | -- AlterTable 5 | ALTER TABLE "playlist_illustrations" ADD COLUMN "aspectRatio" DOUBLE PRECISION NOT NULL DEFAULT 1; 6 | 7 | -- AlterTable 8 | ALTER TABLE "release_illustrations" ADD COLUMN "aspectRatio" DOUBLE PRECISION NOT NULL DEFAULT 1; 9 | 10 | -- AlterTable 11 | ALTER TABLE "track_illustrations" ADD COLUMN "aspectRatio" DOUBLE PRECISION NOT NULL DEFAULT 1; 12 | -------------------------------------------------------------------------------- /server/prisma/migrations/20240111124042_release_illustration/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the `track_illustrations` table. If the table is not empty, all the data it contains will be lost. 5 | - Added the required column `hash` to the `release_illustrations` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- DropForeignKey 9 | ALTER TABLE "track_illustrations" DROP CONSTRAINT "track_illustrations_trackId_fkey"; 10 | 11 | -- AlterTable 12 | ALTER TABLE "release_illustrations" ADD COLUMN "hash" TEXT NOT NULL; 13 | ALTER TABLE "release_illustrations" ADD COLUMN "track" INTEGER; 14 | CREATE UNIQUE INDEX "release_illustrations_releaseId_disc_track_key" ON "release_illustrations"("releaseId", "disc", "track"); 15 | 16 | 17 | -- DropTable 18 | DROP TABLE "track_illustrations"; 19 | -------------------------------------------------------------------------------- /server/prisma/migrations/20240120094441_remastered_track_field/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "tracks" ADD COLUMN "isRemastered" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /server/prisma/migrations/20240120104148_release_name_extensions/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "releases" ADD COLUMN "extensions" TEXT[]; 3 | -------------------------------------------------------------------------------- /server/prisma/migrations/20240131095559_play_history/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `playCount` on the `songs` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "songs" DROP COLUMN "playCount"; 9 | 10 | -- CreateTable 11 | CREATE TABLE "play_history" ( 12 | "id" SERIAL NOT NULL, 13 | "playedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 14 | "userId" INTEGER NOT NULL, 15 | "songId" INTEGER NOT NULL, 16 | 17 | CONSTRAINT "play_history_pkey" PRIMARY KEY ("id") 18 | ); 19 | 20 | -- AddForeignKey 21 | ALTER TABLE "play_history" ADD CONSTRAINT "play_history_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; 22 | 23 | -- AddForeignKey 24 | ALTER TABLE "play_history" ADD CONSTRAINT "play_history_songId_fkey" FOREIGN KEY ("songId") REFERENCES "songs"("id") ON DELETE CASCADE ON UPDATE CASCADE; 25 | -------------------------------------------------------------------------------- /server/prisma/migrations/20240207162850_artist_image_url/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "artist_external_ids" ADD COLUMN "illustration" TEXT; 3 | -------------------------------------------------------------------------------- /server/prisma/migrations/20240603054248_new_illustration_table_artist/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the `artist_illustrations` table. If the table is not empty, all the data it contains will be lost. 5 | - A unique constraint covering the columns `[illustrationId]` on the table `artists` will be added. If there are existing duplicate values, this will fail. 6 | 7 | */ 8 | -- CreateEnum 9 | CREATE TYPE "IllustrationType" AS ENUM ('Cover', 'Avatar', 'Thumbnail'); 10 | 11 | -- DropForeignKey 12 | ALTER TABLE "artist_illustrations" DROP CONSTRAINT "artist_illustrations_artistId_fkey"; 13 | 14 | -- AlterTable 15 | ALTER TABLE "artists" ADD COLUMN "illustrationId" INTEGER; 16 | 17 | -- DropTable 18 | DROP TABLE "artist_illustrations"; 19 | 20 | -- CreateTable 21 | CREATE TABLE "illustrations" ( 22 | "id" SERIAL NOT NULL, 23 | "blurhash" TEXT NOT NULL, 24 | "colors" TEXT[], 25 | "aspectRatio" DOUBLE PRECISION NOT NULL DEFAULT 1, 26 | "type" "IllustrationType" NOT NULL, 27 | 28 | CONSTRAINT "illustrations_pkey" PRIMARY KEY ("id") 29 | ); 30 | 31 | -- CreateIndex 32 | CREATE UNIQUE INDEX "artists_illustrationId_key" ON "artists"("illustrationId"); 33 | 34 | -- AddForeignKey 35 | ALTER TABLE "artists" ADD CONSTRAINT "artists_illustrationId_fkey" FOREIGN KEY ("illustrationId") REFERENCES "illustrations"("id") ON DELETE SET NULL ON UPDATE CASCADE; 36 | -------------------------------------------------------------------------------- /server/prisma/migrations/20240604052515_new_illustration_table_playlist/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the `playlist_illustrations` table. If the table is not empty, all the data it contains will be lost. 5 | - A unique constraint covering the columns `[illustrationId]` on the table `playlists` will be added. If there are existing duplicate values, this will fail. 6 | 7 | */ 8 | -- DropForeignKey 9 | ALTER TABLE "playlist_illustrations" DROP CONSTRAINT "playlist_illustrations_playlistId_fkey"; 10 | 11 | -- AlterTable 12 | ALTER TABLE "playlists" ADD COLUMN "illustrationId" INTEGER; 13 | 14 | -- DropTable 15 | DROP TABLE "playlist_illustrations"; 16 | 17 | -- CreateIndex 18 | CREATE UNIQUE INDEX "playlists_illustrationId_key" ON "playlists"("illustrationId"); 19 | 20 | -- AddForeignKey 21 | ALTER TABLE "playlists" ADD CONSTRAINT "playlists_illustrationId_fkey" FOREIGN KEY ("illustrationId") REFERENCES "illustrations"("id") ON DELETE SET NULL ON UPDATE CASCADE; 22 | -------------------------------------------------------------------------------- /server/prisma/migrations/20240609094144_medley_song_type/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "song-types" ADD VALUE 'Medley'; 3 | -------------------------------------------------------------------------------- /server/prisma/migrations/20240618172303_fix_track_illustration_view/migration.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE VIEW "track_illustrations_view" AS 2 | SELECT 3 | i.*, 4 | t.id as "trackId" 5 | FROM tracks t 6 | JOIN releases r ON r.id = t."releaseId" 7 | JOIN LATERAL 8 | (select "illustrationId" from release_illustrations ri 9 | where ri."releaseId" = r."id" 10 | AND (((ri.disc is not distinct from t."discIndex") AND (ri.track is not distinct from t."trackIndex")) 11 | OR ((ri.disc is not distinct from t."discIndex") AND (ri.track IS NULL)) 12 | OR ((ri.disc IS NULL) AND (ri.track IS NULL))) 13 | ORDER BY track NULLS LAST, disc ASC NULLS LAST LIMIT 1) 14 | as ri ON TRUE 15 | JOIN illustrations i on i.id = ri."illustrationId"; 16 | -------------------------------------------------------------------------------- /server/prisma/migrations/20240906115347_rename_checksum_field/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `md5Checksum` on the `files` table. All the data in the column will be lost. 5 | - Added the required column `checksum` to the `files` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "files" RENAME COLUMN "md5Checksum" TO "checksum" -------------------------------------------------------------------------------- /server/prisma/migrations/20241102151239_search_history/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "search_history" ( 3 | "id" SERIAL NOT NULL, 4 | "searchAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 5 | "songId" INTEGER, 6 | "albumId" INTEGER, 7 | "artistId" INTEGER, 8 | "userId" INTEGER NOT NULL, 9 | 10 | CONSTRAINT "search_history_pkey" PRIMARY KEY ("id") 11 | ); 12 | 13 | -- CreateIndex 14 | CREATE UNIQUE INDEX "search_history_songId_albumId_artistId_userId_key" ON "search_history"("songId", "albumId", "artistId", "userId"); 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "search_history" ADD CONSTRAINT "search_history_songId_fkey" FOREIGN KEY ("songId") REFERENCES "songs"("id") ON DELETE CASCADE ON UPDATE CASCADE; 18 | 19 | -- AddForeignKey 20 | ALTER TABLE "search_history" ADD CONSTRAINT "search_history_albumId_fkey" FOREIGN KEY ("albumId") REFERENCES "albums"("id") ON DELETE CASCADE ON UPDATE CASCADE; 21 | 22 | -- AddForeignKey 23 | ALTER TABLE "search_history" ADD CONSTRAINT "search_history_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES "artists"("id") ON DELETE CASCADE ON UPDATE CASCADE; 24 | 25 | -- AddForeignKey 26 | ALTER TABLE "search_history" ADD CONSTRAINT "search_history_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; 27 | -------------------------------------------------------------------------------- /server/prisma/migrations/20241212090123_fix_acappella_spelling/migration.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE "song-types" RENAME VALUE 'Acapella' TO 'Acappella' 2 | -------------------------------------------------------------------------------- /server/prisma/migrations/20241220080246_add_acoustid/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "files" ADD COLUMN "fingerprint" TEXT; 3 | -------------------------------------------------------------------------------- /server/prisma/migrations/20241221112653_standalone_track/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "tracks" DROP CONSTRAINT "tracks_releaseId_fkey"; 3 | 4 | -- AlterTable 5 | ALTER TABLE "tracks" ALTER COLUMN "releaseId" DROP NOT NULL; 6 | 7 | -- AddForeignKey 8 | ALTER TABLE "tracks" ADD CONSTRAINT "tracks_releaseId_fkey" FOREIGN KEY ("releaseId") REFERENCES "releases"("id") ON DELETE SET NULL ON UPDATE CASCADE; 9 | -------------------------------------------------------------------------------- /server/prisma/migrations/20250102092916_prevent_silent_deletion_of_non_empty_songs/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "videos" 3 | DROP CONSTRAINT "videos_groupId_fkey"; 4 | 5 | -- DropForeignKey 6 | ALTER TABLE "videos" 7 | DROP CONSTRAINT "videos_songId_fkey"; 8 | 9 | -- DropForeignKey 10 | ALTER TABLE "tracks" 11 | DROP CONSTRAINT "tracks_songId_fkey"; 12 | 13 | -- DropForeignKey 14 | ALTER TABLE "tracks" 15 | DROP CONSTRAINT "tracks_videoId_fkey"; 16 | 17 | -- AddForeignKey 18 | ALTER TABLE "videos" 19 | ADD CONSTRAINT "videos_songId_fkey" FOREIGN KEY ("songId") REFERENCES "songs" ("id") ON DELETE RESTRICT ON UPDATE CASCADE; 20 | 21 | -- AddForeignKey 22 | ALTER TABLE "videos" 23 | ADD CONSTRAINT "videos_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "song_groups" ("id") ON DELETE RESTRICT ON UPDATE CASCADE; 24 | 25 | -- AddForeignKey 26 | ALTER TABLE "tracks" 27 | ADD CONSTRAINT "tracks_songId_fkey" FOREIGN KEY ("songId") REFERENCES "songs" ("id") ON DELETE RESTRICT ON UPDATE CASCADE; 28 | 29 | -- AddForeignKey 30 | ALTER TABLE "tracks" 31 | ADD CONSTRAINT "tracks_videoId_fkey" FOREIGN KEY ("videoId") REFERENCES "videos" ("id") ON DELETE RESTRICT ON UPDATE CASCADE; 32 | 33 | -------------------------------------------------------------------------------- /server/prisma/migrations/20250102110631_video_add_registration_date/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "videos" 3 | ADD COLUMN "registeredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; 4 | 5 | -------------------------------------------------------------------------------- /server/prisma/migrations/20250104154901_video_master_track/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[masterId]` on the table `videos` will be added. If there are existing duplicate values, this will fail. 5 | */ 6 | -- AlterTable 7 | ALTER TABLE "videos" 8 | ADD COLUMN "masterId" INTEGER; 9 | 10 | -- CreateIndex 11 | CREATE UNIQUE INDEX "videos_masterId_key" ON "videos" ("masterId"); 12 | 13 | -- AddForeignKey 14 | ALTER TABLE "videos" 15 | ADD CONSTRAINT "videos_masterId_fkey" FOREIGN KEY ("masterId") REFERENCES "tracks" ("id") ON DELETE SET NULL ON UPDATE CASCADE; 16 | 17 | CREATE VIEW "video_illustrations_view" AS 18 | SELECT 19 | i.*, 20 | v.id AS "videoId" 21 | FROM 22 | videos v 23 | JOIN LATERAL ( 24 | SELECT 25 | id 26 | FROM 27 | tracks 28 | WHERE (id = v."masterId") 29 | OR ("videoId" = v.id) 30 | ORDER BY 31 | bitrate DESC NULLS LAST 32 | LIMIT 1) t ON TRUE 33 | JOIN track_illustrations_view ti ON ti."trackId" = COALESCE(v."masterId", t.id) 34 | JOIN illustrations i ON i.id = ti.id; 35 | 36 | -------------------------------------------------------------------------------- /server/prisma/migrations/20250105130635_search_history_add_videos/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[songId,albumId,artistId,videoId,userId]` on the table `search_history` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- DropIndex 8 | DROP INDEX "search_history_songId_albumId_artistId_userId_key"; 9 | 10 | -- AlterTable 11 | ALTER TABLE "search_history" ADD COLUMN "videoId" INTEGER; 12 | 13 | -- CreateIndex 14 | CREATE UNIQUE INDEX "search_history_songId_albumId_artistId_videoId_userId_key" ON "search_history"("songId", "albumId", "artistId", "videoId", "userId"); 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "search_history" ADD CONSTRAINT "search_history_videoId_fkey" FOREIGN KEY ("videoId") REFERENCES "videos"("id") ON DELETE CASCADE ON UPDATE CASCADE; 18 | -------------------------------------------------------------------------------- /server/prisma/migrations/20250117200829_fix_many_to_many_prisma_breaking/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "_AlbumToGenre" ADD CONSTRAINT "_AlbumToGenre_AB_pkey" PRIMARY KEY ("A", "B"); 3 | 4 | -- DropIndex 5 | DROP INDEX "_AlbumToGenre_AB_unique"; 6 | 7 | -- AlterTable 8 | ALTER TABLE "_ArtistToSong" ADD CONSTRAINT "_ArtistToSong_AB_pkey" PRIMARY KEY ("A", "B"); 9 | 10 | -- DropIndex 11 | DROP INDEX "_ArtistToSong_AB_unique"; 12 | 13 | -- AlterTable 14 | ALTER TABLE "_GenreToSong" ADD CONSTRAINT "_GenreToSong_AB_pkey" PRIMARY KEY ("A", "B"); 15 | 16 | -- DropIndex 17 | DROP INDEX "_GenreToSong_AB_unique"; 18 | -------------------------------------------------------------------------------- /server/prisma/migrations/20250305132915_song_bpm/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "songs" ADD COLUMN "bpm" DOUBLE PRECISION; 3 | -------------------------------------------------------------------------------- /server/prisma/migrations/20250308201912_lyrics_rename_plain_column/migration.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "lyrics" RENAME COLUMN "content" TO "plain"; 2 | 3 | -------------------------------------------------------------------------------- /server/prisma/migrations/20250308203912_synced_lyrics_column/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "lyrics" 3 | ADD COLUMN "synced" JSONB; 4 | 5 | -------------------------------------------------------------------------------- /server/prisma/migrations/20250313195107_discs/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "tracks" 3 | ADD COLUMN "discName" TEXT; 4 | 5 | CREATE VIEW "discs" AS ( 6 | SELECT 7 | DISTINCT ON ("releaseId", "discIndex") 8 | "id", 9 | "releaseId", 10 | "discIndex" AS "index", 11 | "discName" AS "name" 12 | FROM 13 | "tracks" 14 | ORDER BY 15 | "releaseId", 16 | "index", 17 | "name" ASC NULLS LAST); 18 | 19 | -------------------------------------------------------------------------------- /server/prisma/migrations/20250426090635_playlist_visibility/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "playlists" 3 | ADD COLUMN "allowChanges" boolean NOT NULL DEFAULT FALSE, 4 | ADD COLUMN "isPublic" boolean NOT NULL DEFAULT TRUE, 5 | ADD COLUMN "ownerId" integer; 6 | 7 | UPDATE 8 | "playlists" 9 | SET 10 | "allowChanges" = FALSE, 11 | "isPublic" = TRUE, 12 | "ownerId" = ( 13 | SELECT 14 | id 15 | FROM 16 | "users" 17 | WHERE 18 | "enabled" = TRUE 19 | ORDER BY 20 | id ASC 21 | LIMIT 1); 22 | 23 | -- AddForeignKey 24 | ALTER TABLE "playlists" 25 | ADD CONSTRAINT "playlists_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE; 26 | 27 | ALTER TABLE "playlists" 28 | ALTER COLUMN "allowChanges" DROP DEFAULT, 29 | ALTER COLUMN "isPublic" DROP DEFAULT, 30 | ALTER COLUMN "ownerId" SET NOT NULL; 31 | 32 | -------------------------------------------------------------------------------- /server/prisma/migrations/20250504082315_labels/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "releases" ADD COLUMN "labelId" INTEGER; 3 | 4 | -- CreateTable 5 | CREATE TABLE "labels" ( 6 | "id" SERIAL NOT NULL, 7 | "name" CITEXT NOT NULL, 8 | "slug" TEXT NOT NULL, 9 | 10 | CONSTRAINT "labels_pkey" PRIMARY KEY ("id") 11 | ); 12 | 13 | -- CreateIndex 14 | CREATE UNIQUE INDEX "labels_slug_key" ON "labels"("slug"); 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "releases" ADD CONSTRAINT "releases_labelId_fkey" FOREIGN KEY ("labelId") REFERENCES "labels"("id") ON DELETE SET NULL ON UPDATE CASCADE; 18 | -------------------------------------------------------------------------------- /server/prisma/migrations/20250505171258_mixed_tag/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "tracks" ADD COLUMN "mixed" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /server/prisma/migrations/20250518085251_scrobblers/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "scrobblers" AS ENUM ( 3 | 'LastFM', 4 | 'ListenBrainz' 5 | ); 6 | 7 | -- CreateTable 8 | CREATE TABLE "user_scrobbler" ( 9 | "userId" integer NOT NULL, 10 | "lastScrobblingDate" timestamp(3), 11 | "scrobbler" "scrobblers" NOT NULL, 12 | "data" jsonb NOT NULL, 13 | CONSTRAINT "user_scrobbler_pkey" PRIMARY KEY ("scrobbler", "userId") 14 | ); 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "user_scrobbler" 18 | ADD CONSTRAINT "user_scrobbler_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE; 19 | 20 | -------------------------------------------------------------------------------- /server/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (e.g., Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /server/prisma/types.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | namespace PrismaJson { 3 | type SyncedLyrics = [{ timestamp: number; content: string }]; 4 | type LastFMData = { sessionToken: string }; 5 | type ListenBrainzData = { 6 | userToken: string; 7 | instanceUrl: string | null; 8 | }; 9 | } 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /server/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=arthi-chaud_Meelo-back 2 | sonar.organization=arthi-chaud 3 | 4 | # This is the name and version displayed in the SonarCloud UI. 5 | sonar.projectName=Meelo (Back) 6 | #sonar.projectVersion=1.0 7 | 8 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 9 | #sonar.sources=. 10 | 11 | # Define the same root directory for sources and tests 12 | sonar.sources = src 13 | sonar.tests = src 14 | 15 | # Include test subdirectories in test scope 16 | sonar.test.inclusions = src/**/*.spec.ts 17 | 18 | # Exclude test subdirectories from source scope 19 | sonar.exclusions = src/**/*.spec.ts,src/**/models/*.dto.ts,src/public/**/*.js 20 | 21 | sonar.typescript.lcov.reportPaths = coverage/lcov.info -------------------------------------------------------------------------------- /server/src/album/models/album.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import type { AlbumType } from "@prisma/client"; 20 | import type { ArtistModel } from "src/artist/models/artist.model"; 21 | import type { Release } from "src/prisma/models"; 22 | 23 | export type AlbumModel = { 24 | id: number; 25 | name: string; 26 | slug: string; 27 | releaseDate: Date | null; 28 | registeredAt: Date; 29 | masterId: number | null; 30 | master?: Release | null; 31 | type: AlbumType; 32 | artistId: number | null; 33 | artist?: ArtistModel | null; 34 | releases?: Release[]; 35 | }; 36 | -------------------------------------------------------------------------------- /server/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { Controller, Get } from "@nestjs/common"; 20 | import { ApiExcludeController } from "@nestjs/swagger"; 21 | import { Public } from "./authentication/roles/roles.decorators"; 22 | 23 | @ApiExcludeController() 24 | @Controller() 25 | export default class AppController { 26 | @Get() 27 | @Public() 28 | welcome() { 29 | return { 30 | message: 31 | "Welcome to Meelo! To know more about the API, visit '/swagger'", 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /server/src/artist/models/artist.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export type ArtistModel = { 20 | id: number; 21 | name: string; 22 | slug: string; 23 | registeredAt: Date; 24 | }; 25 | -------------------------------------------------------------------------------- /server/src/authentication/models/auth.enum.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | // The Authentication Method used for the request 20 | export enum AuthMethod { 21 | Nothing, 22 | JWT, 23 | ApiKey, 24 | } 25 | -------------------------------------------------------------------------------- /server/src/authentication/models/jwt.models.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ApiProperty, PickType } from "@nestjs/swagger"; 20 | import { User } from "src/prisma/models"; 21 | 22 | /** 23 | * Response type on login 24 | */ 25 | export class JwtResponse { 26 | @ApiProperty({ 27 | description: 28 | "JWT Access Token. To add to request's header for authenticated requests", 29 | }) 30 | access_token: string; 31 | } 32 | 33 | /** 34 | * Type of the decoded JWT Payload 35 | */ 36 | export class JwtPayload extends PickType(User, ["id", "name"]) {} 37 | -------------------------------------------------------------------------------- /server/src/authentication/models/login.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ApiProperty } from "@nestjs/swagger"; 20 | import { IsNotEmpty, IsString } from "class-validator"; 21 | 22 | export default class LoginDTO { 23 | @ApiProperty({ 24 | required: true, 25 | description: "The user's username", 26 | }) 27 | @IsString() 28 | @IsNotEmpty() 29 | username: string; 30 | 31 | @ApiProperty({ 32 | required: true, 33 | description: "The plain password of the user", 34 | }) 35 | @IsString() 36 | @IsNotEmpty() 37 | password: string; 38 | } 39 | -------------------------------------------------------------------------------- /server/src/authentication/roles/roles.enum.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | enum Roles { 20 | Admin, 21 | User, 22 | Default, // User | Anonymous, depending on settings 23 | Microservice, 24 | Anonymous, 25 | } 26 | 27 | export default Roles; 28 | -------------------------------------------------------------------------------- /server/src/constants/compilation.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | /** 20 | * !! Changing this value is a breaking change !! 21 | */ 22 | const compilationAlbumArtistKeyword = "compilations"; 23 | 24 | export default compilationAlbumArtistKeyword; 25 | -------------------------------------------------------------------------------- /server/src/events/events.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { Module } from "@nestjs/common"; 20 | import { EventsService } from "./events.service"; 21 | 22 | @Module({ 23 | imports: [], 24 | providers: [EventsService], 25 | exports: [EventsService], 26 | }) 27 | export class EventsModule {} 28 | -------------------------------------------------------------------------------- /server/src/exceptions/not-found.exception.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { 20 | type ArgumentsHost, 21 | Catch, 22 | type ExceptionFilter, 23 | HttpStatus, 24 | NotFoundException, 25 | } from "@nestjs/common"; 26 | 27 | @Catch(NotFoundException) 28 | export default class NotFoundExceptionFilter implements ExceptionFilter { 29 | catch(_exception: NotFoundException, host: ArgumentsHost) { 30 | const ctx = host.switchToHttp(); 31 | const response = ctx.getResponse(); 32 | 33 | response.status(HttpStatus.NOT_FOUND).json({ 34 | statusCode: HttpStatus.NOT_FOUND, 35 | message: "Route not found.", 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /server/src/external-metadata/models/provider.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { PickType } from "@nestjs/swagger"; 20 | import IllustrationRegistrationDto from "src/illustration/models/illustration-registration.dto"; 21 | import { CreateProvider } from "src/prisma/models"; 22 | 23 | export class CreateProviderDTO extends PickType(CreateProvider, ["name"]) {} 24 | 25 | export class ProviderIconRegistrationDto extends PickType( 26 | IllustrationRegistrationDto, 27 | ["file"], 28 | ) {} 29 | -------------------------------------------------------------------------------- /server/src/external-metadata/models/provider.query-parameters.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import type Slug from "src/slug/slug"; 20 | import type { RequireExactlyOne } from "type-fest"; 21 | 22 | namespace ProviderQueryParameters { 23 | /** 24 | * Query parameters to find one provider 25 | */ 26 | export type WhereInput = RequireExactlyOne<{ 27 | id: number; 28 | slug: Slug; 29 | }>; 30 | } 31 | 32 | export default ProviderQueryParameters; 33 | -------------------------------------------------------------------------------- /server/src/file-manager/file-manager.exceptions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { NotFoundException } from "src/exceptions/meelo-exception"; 20 | 21 | export class FileDoesNotExistException extends NotFoundException { 22 | constructor(fileName: string) { 23 | super(`${fileName}: No such file`); 24 | } 25 | } 26 | export class FolderDoesNotExistException extends NotFoundException { 27 | constructor(folderPath: string) { 28 | super(`Folder '${folderPath}' does not exist`); 29 | } 30 | } 31 | 32 | export class FileNotReadableException extends NotFoundException { 33 | constructor(fileName: string) { 34 | super(`${fileName}: Permission denied`); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /server/src/file-manager/file-manager.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { Global, Module, forwardRef } from "@nestjs/common"; 20 | import SettingsModule from "src/settings/settings.module"; 21 | import FileManagerService from "./file-manager.service"; 22 | 23 | @Global() 24 | @Module({ 25 | imports: [forwardRef(() => SettingsModule)], 26 | providers: [FileManagerService], 27 | exports: [FileManagerService], 28 | }) 29 | export default class FileManagerModule {} 30 | -------------------------------------------------------------------------------- /server/src/file/models/file-deletion.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ApiProperty } from "@nestjs/swagger"; 20 | import { IsArray, IsInt } from "class-validator"; 21 | 22 | export default class FileDeletionDto { 23 | @ApiProperty() 24 | @IsArray() 25 | @IsInt({ each: true }) 26 | ids: number[]; 27 | } 28 | -------------------------------------------------------------------------------- /server/src/identifier/identifier.exceptions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { InvalidRequestException } from "src/exceptions/meelo-exception"; 20 | import type Identifier from "./models/identifier"; 21 | 22 | export default class InvalidIdentifierSlugs extends InvalidRequestException { 23 | constructor( 24 | identifier: Identifier, 25 | expectedTokens: number, 26 | actualTokenCount: number, 27 | ) { 28 | super( 29 | `Parsing slugs from identifier '${identifier}' failed: Expected ${expectedTokens} slugs, got ${actualTokenCount}`, 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /server/src/identifier/models/identifier.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | type Identifier = number | string; 20 | 21 | export const castIdentifier = (s: string): Identifier => { 22 | if (Number.isNaN(+s)) { 23 | return s; 24 | } 25 | return Number.parseInt(s); 26 | }; 27 | 28 | export default Identifier; 29 | -------------------------------------------------------------------------------- /server/src/illustration/models/illustration-path.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export type IllustrationPath = string; 20 | /** 21 | * A absolute path to an illustration folder 22 | */ 23 | export type IllustrationFolderPath = string; 24 | -------------------------------------------------------------------------------- /server/src/illustration/models/illustration-quality.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export const ImageQuality = ["low", "medium", "high"] as const; 20 | 21 | export type ImageQuality = (typeof ImageQuality)[number]; 22 | -------------------------------------------------------------------------------- /server/src/illustration/models/illustration-stats.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import type { Illustration } from "@prisma/client"; 20 | 21 | export default class IllustrationStats { 22 | blurhash: string; 23 | colors: string[]; 24 | aspectRatio: number; 25 | static from(illustration: Illustration): IllustrationStats { 26 | return { 27 | blurhash: illustration.blurhash, 28 | colors: illustration.colors, 29 | aspectRatio: illustration.aspectRatio, 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /server/src/label/label.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { Module } from "@nestjs/common"; 20 | import PrismaModule from "src/prisma/prisma.module"; 21 | import LabelController from "./label.controller"; 22 | import LabelService from "./label.service"; 23 | 24 | @Module({ 25 | imports: [PrismaModule], 26 | providers: [LabelService], 27 | exports: [LabelService], 28 | controllers: [LabelController], 29 | }) 30 | export default class LabelModule {} 31 | -------------------------------------------------------------------------------- /server/src/library/models/update-library.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { PartialType } from "@nestjs/swagger"; 20 | import CreateLibraryDto from "./create-library.dto"; 21 | 22 | export default class UpdatelibraryDTO extends PartialType(CreateLibraryDto) {} 23 | -------------------------------------------------------------------------------- /server/src/logger/logger.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { Module } from "@nestjs/common"; 20 | import Logger from "./logger"; 21 | 22 | @Module({ 23 | imports: [], 24 | providers: [Logger], 25 | exports: [Logger], 26 | }) 27 | export default class LoggerModule {} 28 | -------------------------------------------------------------------------------- /server/src/lyrics/lyrics.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { Module, forwardRef } from "@nestjs/common"; 20 | import PrismaModule from "src/prisma/prisma.module"; 21 | import SongModule from "src/song/song.module"; 22 | import { LyricsService } from "./lyrics.service"; 23 | 24 | @Module({ 25 | providers: [LyricsService], 26 | exports: [LyricsService], 27 | imports: [PrismaModule, forwardRef(() => SongModule)], 28 | }) 29 | export class LyricsModule {} 30 | -------------------------------------------------------------------------------- /server/src/lyrics/models/lyrics.query-parameters.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { SyncedLyric } from "./lyrics.response"; 20 | 21 | namespace LyricsQueryParameters { 22 | /** 23 | * Parameters required to create a Lyric entry 24 | */ 25 | export type CreateInput = { 26 | plain: string; 27 | synced?: SyncedLyric[]; 28 | songId: number; 29 | }; 30 | /** 31 | * Query parameters to find one lyric entry 32 | */ 33 | export type WhereInput = { 34 | songId: number; 35 | }; 36 | } 37 | 38 | export default LyricsQueryParameters; 39 | -------------------------------------------------------------------------------- /server/src/pagination/pagination.exceptions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { InvalidRequestException } from "src/exceptions/meelo-exception"; 20 | import type { PaginationParameters } from "./models/pagination-parameters"; 21 | 22 | export default class InvalidPaginationParameterValue extends InvalidRequestException { 23 | constructor(key: keyof PaginationParameters) { 24 | super(`Invalid '${key}' parameter: expected positive integer`); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/src/parser/parser.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { Module, forwardRef } from "@nestjs/common"; 20 | import ArtistModule from "src/artist/artist.module"; 21 | import ParserService from "./parser.service"; 22 | 23 | @Module({ 24 | imports: [forwardRef(() => ArtistModule)], 25 | providers: [ParserService], 26 | exports: [ParserService], 27 | }) 28 | export default class ParserModule {} 29 | -------------------------------------------------------------------------------- /server/src/playlist/models/playlist-entry.model.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ApiProperty } from "@nestjs/swagger"; 20 | import { SongWithRelations } from "src/prisma/models"; 21 | 22 | export class PlaylistEntryModel extends SongWithRelations { 23 | @ApiProperty() 24 | entryId: number; 25 | @ApiProperty() 26 | index: number; 27 | } 28 | -------------------------------------------------------------------------------- /server/src/prisma/prisma.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { Module } from "@nestjs/common"; 20 | import PrismaService from "./prisma.service"; 21 | 22 | @Module({ 23 | providers: [PrismaService], 24 | exports: [PrismaService], 25 | }) 26 | export default class PrismaModule {} 27 | -------------------------------------------------------------------------------- /server/src/registration/models/metadata-saved.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ApiProperty } from "@nestjs/swagger"; 20 | 21 | export default class MetadataSavedResponse { 22 | @ApiProperty() 23 | trackId: number; 24 | @ApiProperty() 25 | libraryId: number; 26 | @ApiProperty() 27 | videoId: number | null; 28 | @ApiProperty() 29 | songId: number | null; 30 | @ApiProperty() 31 | sourceFileId: number; 32 | @ApiProperty() 33 | releaseId: number | null; 34 | } 35 | -------------------------------------------------------------------------------- /server/src/relation-include/atomic-relation-include.filter.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export function filterAtomicRelationInclude( 20 | keys: readonly Key[], 21 | toKeep?: Key[], 22 | ) { 23 | const keysCopy = Array.from(keys); 24 | 25 | return keysCopy.filter( 26 | (key) => toKeep?.includes(key) || !key.endsWith("s"), 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /server/src/relation-include/models/relation-include.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export type RelationInclude = Partial< 20 | Record 21 | >; 22 | -------------------------------------------------------------------------------- /server/src/relation-include/relation-include-route.decorator.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ApiQuery } from "@nestjs/swagger"; 20 | 21 | export function ApiRelationInclude(keys: readonly string[], name = "with") { 22 | return ApiQuery({ 23 | name, 24 | required: false, 25 | description: "The relations to include with the returned object", 26 | type: String, 27 | isArray: true, 28 | style: "label", 29 | enum: keys, 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /server/src/release/models/reassign-release.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ApiProperty } from "@nestjs/swagger"; 20 | import { IsNumber } from "class-validator"; 21 | import type { Album } from "src/prisma/models"; 22 | 23 | export default class ReassignReleaseDTO { 24 | @ApiProperty({ 25 | description: "The ID of the album to reassign the release to", 26 | example: 124, 27 | }) 28 | @IsNumber() 29 | albumId: Album["id"]; 30 | } 31 | -------------------------------------------------------------------------------- /server/src/response/response-type.enum.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | enum ResponseType { 20 | Single, 21 | Array, 22 | Page, 23 | } 24 | 25 | export default ResponseType; 26 | -------------------------------------------------------------------------------- /server/src/scrobbler/models/scrobblers.response.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ApiProperty } from "@nestjs/swagger"; 20 | import { Scrobbler } from "@prisma/client"; 21 | 22 | export default class ScrobblersResponse { 23 | @ApiProperty({ 24 | description: "The scrobblers enabled by the user", 25 | isArray: true, 26 | enum: Scrobbler, 27 | }) 28 | connected: Scrobbler[]; 29 | 30 | @ApiProperty({ 31 | description: "The scrobblers that are not enabled by the user", 32 | isArray: true, 33 | enum: Scrobbler, 34 | }) 35 | available: Scrobbler[]; 36 | } 37 | -------------------------------------------------------------------------------- /server/src/scrobbler/scrobbler.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { HttpModule } from "@nestjs/axios"; 20 | import { Module } from "@nestjs/common"; 21 | import PrismaModule from "src/prisma/prisma.module"; 22 | import ScrobblerController from "./scrobbler.controller"; 23 | import ScrobblerService from "./scrobbler.service"; 24 | 25 | @Module({ 26 | providers: [ScrobblerService], 27 | exports: [ScrobblerService], 28 | imports: [PrismaModule, HttpModule], 29 | controllers: [ScrobblerController], 30 | }) 31 | export default class ScrobblerModule {} 32 | -------------------------------------------------------------------------------- /server/src/search/models/create-search-history-entry.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ApiPropertyOptional } from "@nestjs/swagger"; 20 | import { IsOptional, IsPositive } from "class-validator"; 21 | 22 | export class CreateSearchHistoryEntry { 23 | @ApiPropertyOptional() 24 | @IsPositive() 25 | @IsOptional() 26 | songId?: number; 27 | @ApiPropertyOptional() 28 | @IsPositive() 29 | @IsOptional() 30 | albumId?: number; 31 | @ApiPropertyOptional() 32 | @IsPositive() 33 | @IsOptional() 34 | videoId?: number; 35 | @ApiPropertyOptional() 36 | @IsPositive() 37 | @IsOptional() 38 | artistId?: number; 39 | } 40 | -------------------------------------------------------------------------------- /server/src/search/search.utils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import type { Artist, Song, Video } from "@prisma/client"; 20 | import type { AlbumModel } from "src/album/models/album.model"; 21 | 22 | // Use this is you have a union type and need to identify what type is actually is 23 | export function getSearchResourceType( 24 | item: Artist | AlbumModel | Song | Video, 25 | ) { 26 | if ("groupId" in item) { 27 | if ("songId" in item) { 28 | return "video"; 29 | } 30 | return "song"; 31 | } 32 | if ("masterId" in item) { 33 | return "album"; 34 | } 35 | return "artist"; 36 | } 37 | -------------------------------------------------------------------------------- /server/src/settings/settings.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { Module, forwardRef } from "@nestjs/common"; 20 | import FileManagerModule from "src/file-manager/file-manager.module"; 21 | import SettingsController from "./settings.controller"; 22 | import SettingsService from "./settings.service"; 23 | 24 | @Module({ 25 | imports: [forwardRef(() => FileManagerModule)], 26 | providers: [SettingsService, SettingsController], 27 | exports: [SettingsService, SettingsController], 28 | controllers: [SettingsController], 29 | }) 30 | export default class SettingsModule {} 31 | -------------------------------------------------------------------------------- /server/src/settings/settings.service.spec.ts: -------------------------------------------------------------------------------- 1 | import type { TestingModule } from "@nestjs/testing"; 2 | import FileManagerModule from "src/file-manager/file-manager.module"; 3 | import FileManagerService from "src/file-manager/file-manager.service"; 4 | import { createTestingModule } from "test/test-module"; 5 | import SettingsModule from "./settings.module"; 6 | import SettingsService from "./settings.service"; 7 | 8 | describe("Settings Service", () => { 9 | let settingsService: SettingsService; 10 | let fileManagerService: FileManagerService; 11 | let moduleRef: TestingModule; 12 | beforeAll(async () => { 13 | moduleRef = await createTestingModule({ 14 | imports: [SettingsModule, FileManagerModule], 15 | }).compile(); 16 | fileManagerService = moduleRef.get(FileManagerService); 17 | settingsService = moduleRef.get(SettingsService); 18 | }); 19 | 20 | afterAll(async () => { 21 | await moduleRef.close(); 22 | }); 23 | 24 | describe("Load Env", () => { 25 | it("should sets all values if valid setting file", async () => { 26 | expect(settingsService.settingsValues).toMatchObject({ 27 | dataFolder: "test/assets/", 28 | meeloFolder: "test/assets/", 29 | allowAnonymous: false, 30 | }); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /server/src/song/models/merge-song.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ApiProperty } from "@nestjs/swagger"; 20 | import { IsDefined, IsNumber } from "class-validator"; 21 | 22 | export default class MergeSongDTO { 23 | @ApiProperty({ 24 | description: "The ID of the song to merge with", 25 | isArray: true, 26 | }) 27 | @IsDefined() 28 | @IsNumber() 29 | songId: number; 30 | } 31 | -------------------------------------------------------------------------------- /server/src/sort/models/sorting-order.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export const availableSortingOrders = ["asc", "desc"] as const; 20 | type SortingOrder = (typeof availableSortingOrders)[number]; 21 | export default SortingOrder; 22 | -------------------------------------------------------------------------------- /server/src/stream/stream.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { HttpModule } from "@nestjs/axios"; 20 | import { Module } from "@nestjs/common"; 21 | import FileModule from "src/file/file.module"; 22 | import { StreamController } from "./stream.controller"; 23 | import { StreamService } from "./stream.service"; 24 | 25 | @Module({ 26 | imports: [FileModule, HttpModule], 27 | providers: [StreamService], 28 | controllers: [StreamController], 29 | }) 30 | export class StreamModule {} 31 | -------------------------------------------------------------------------------- /server/src/track/models/update-track.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ApiProperty } from "@nestjs/swagger"; 20 | import { IsNumber } from "class-validator"; 21 | import type { Song } from "src/prisma/models"; 22 | 23 | export default class UpdateTrackDTO { 24 | @ApiProperty({ 25 | description: "The ID of the song to reassign the track to", 26 | example: 2, 27 | }) 28 | @IsNumber() 29 | songId: Song["id"]; 30 | } 31 | -------------------------------------------------------------------------------- /server/src/user/models/update-user.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { PartialType, PickType } from "@nestjs/swagger"; 20 | import { User } from "src/prisma/models"; 21 | 22 | export default class UpdateUserDTO extends PartialType( 23 | PickType(User, ["admin", "enabled", "name"]), 24 | ) {} 25 | -------------------------------------------------------------------------------- /server/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { Module } from "@nestjs/common"; 20 | import PrismaModule from "src/prisma/prisma.module"; 21 | import { UserResponseBuilder } from "./models/user.response"; 22 | import UserController from "./user.controller"; 23 | import UserService from "./user.service"; 24 | 25 | @Module({ 26 | imports: [PrismaModule], 27 | providers: [UserService, UserResponseBuilder], 28 | controllers: [UserController], 29 | exports: [UserService, UserResponseBuilder], 30 | }) 31 | export default class UserModule {} 32 | -------------------------------------------------------------------------------- /server/src/utils/escape-regex.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | const escapeRegex = (str: string) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); 20 | 21 | export default escapeRegex; 22 | -------------------------------------------------------------------------------- /server/src/utils/is-fulfilled.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export const isFulfilled = ( 20 | item: PromiseSettledResult, 21 | ): item is PromiseFulfilledResult => { 22 | return item.status === "fulfilled"; 23 | }; 24 | -------------------------------------------------------------------------------- /server/src/utils/is-undefined.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | export const isUndefined = (item: T | undefined): item is undefined => { 20 | return item === undefined; 21 | }; 22 | 23 | export const isDefined = (item: T | undefined): item is T => { 24 | return !isUndefined(item); 25 | }; 26 | -------------------------------------------------------------------------------- /server/src/utils/search-string-input.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { Prisma } from "@prisma/client"; 20 | import type { RequireExactlyOne } from "type-fest"; 21 | 22 | export type SearchStringInput = RequireExactlyOne<{ 23 | startsWith: string; 24 | endsWith: string; 25 | contains: string; 26 | is: string; 27 | }>; 28 | 29 | export function buildStringSearchParameters(where?: SearchStringInput) { 30 | return { 31 | startsWith: where?.startsWith, 32 | endsWith: where?.endsWith, 33 | contains: where?.contains, 34 | equals: where?.is, 35 | mode: Prisma.QueryMode.insensitive, 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /server/src/utils/shuffle.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | // Stolen from https://stackoverflow.com/questions/16801687/javascript-random-ordering-with-seed 20 | 21 | export function shuffle(seed: number, array: T[]) { 22 | let m = array.length; 23 | while (m) { 24 | const i = Math.floor(random(seed) * m--); 25 | const t = array[m]; 26 | array[m] = array[i]; 27 | array[i] = t; 28 | ++seed; 29 | } 30 | 31 | return array; 32 | } 33 | 34 | function random(seed: number) { 35 | const x = Math.sin(seed++) * 10000; 36 | return x - Math.floor(x); 37 | } 38 | -------------------------------------------------------------------------------- /server/src/video/models/update-video.dto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Meelo is a music server and application to enjoy your personal music files anywhere, anytime you want. 3 | * Copyright (C) 2023 4 | * 5 | * Meelo is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * Meelo is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | import { ApiProperty } from "@nestjs/swagger"; 20 | import { VideoType } from "@prisma/client"; 21 | import { IsEnum, IsOptional } from "class-validator"; 22 | 23 | export default class UpdateVideoDTO { 24 | @ApiProperty({ 25 | description: "The type of the video", 26 | enum: VideoType, 27 | }) 28 | @IsEnum(VideoType) 29 | @IsOptional() 30 | type?: VideoType; 31 | 32 | @ApiProperty({ 33 | description: "ID of the track to set as master", 34 | }) 35 | @IsOptional() 36 | masterTrackId?: number; 37 | } 38 | -------------------------------------------------------------------------------- /server/test/assets/artwork.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/server/test/assets/artwork.jpeg -------------------------------------------------------------------------------- /server/test/assets/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/server/test/assets/cover.jpg -------------------------------------------------------------------------------- /server/test/assets/cover1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/server/test/assets/cover1.jpg -------------------------------------------------------------------------------- /server/test/assets/cover2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/server/test/assets/cover2.jpg -------------------------------------------------------------------------------- /server/test/assets/dreams.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Arthi-chaud/Meelo/7bb77e9538fd80262eddc25f61e35dd15b191e89/server/test/assets/dreams.m4a -------------------------------------------------------------------------------- /server/test/sequencer.ts: -------------------------------------------------------------------------------- 1 | const Sequencer = require("@jest/test-sequencer").default; 2 | 3 | class CustomSequencer extends Sequencer { 4 | sort(tests) { 5 | const copyTests = Array.from(tests); 6 | return copyTests.sort((testA, testB) => 7 | testA.path > testB.path ? 1 : -1, 8 | ); 9 | } 10 | } 11 | 12 | module.exports = CustomSequencer; 13 | -------------------------------------------------------------------------------- /server/test/setup-app.ts: -------------------------------------------------------------------------------- 1 | import type { INestApplication } from "@nestjs/common"; 2 | import type { TestingModule } from "@nestjs/testing"; 3 | import * as Plugins from "../src/app.plugins"; 4 | 5 | export default async function SetupApp( 6 | module: TestingModule, 7 | ): Promise { 8 | const app = module.createNestApplication(); 9 | 10 | app.useGlobalFilters(...Plugins.buildExceptionFilters(app)) 11 | .useGlobalPipes(...Plugins.buildPipes(app)) 12 | .useGlobalInterceptors(...Plugins.buildInterceptors(app)) 13 | .use(...Plugins.buildHttpPlugs(app)); 14 | return app.init(); 15 | } 16 | -------------------------------------------------------------------------------- /server/test/test-module.ts: -------------------------------------------------------------------------------- 1 | import { Module, type ModuleMetadata } from "@nestjs/common"; 2 | import { Test } from "@nestjs/testing"; 3 | import { MemoryStoredFile, NestjsFormDataModule } from "nestjs-form-data"; 4 | import { MeiliSearchModule } from "nestjs-meilisearch"; 5 | import { EventsModule } from "src/events/events.module"; 6 | import { EventsService } from "src/events/events.service"; 7 | 8 | @Module({}) 9 | class MockEventsService { 10 | constructor() {} 11 | publishItemCreationEvent(resourceType: any, name: string, id: number) {} 12 | } 13 | 14 | @Module({ 15 | imports: [], 16 | providers: [{ provide: EventsService, useClass: MockEventsService }], 17 | exports: [{ provide: EventsService, useClass: MockEventsService }], 18 | }) 19 | export class MockEventsModule {} 20 | 21 | export function createTestingModule(metadata: ModuleMetadata) { 22 | return Test.createTestingModule({ 23 | imports: metadata.imports?.concat( 24 | MeiliSearchModule.forRoot({ 25 | host: process.env.MEILI_HOST ?? "localhost:7700", 26 | apiKey: process.env.MEILI_MASTER_KEY, 27 | }), 28 | NestjsFormDataModule.config({ 29 | storage: MemoryStoredFile, 30 | isGlobal: true, 31 | cleanupAfterSuccessHandle: true, 32 | cleanupAfterFailedHandle: true, 33 | }), 34 | EventsModule, 35 | ), 36 | exports: [...(metadata.exports ?? [])], 37 | providers: [...(metadata.providers ?? [])], 38 | }) 39 | .overrideModule(EventsModule) 40 | .useModule(MockEventsModule); 41 | } 42 | -------------------------------------------------------------------------------- /server/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "test", 6 | "dist", 7 | "**/*spec.ts", 8 | "front", 9 | "docs", 10 | "coverage" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "paths": { 14 | "*": ["*", "src/*"] 15 | }, 16 | "incremental": true, 17 | "skipLibCheck": true, 18 | "strictNullChecks": true, 19 | "noImplicitAny": true, 20 | "strictBindCallApply": false, 21 | "forceConsistentCasingInFileNames": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "resolveJsonModule": true, 24 | "esModuleInterop": true, 25 | "alwaysStrict": true, 26 | "noImplicitReturns": true, 27 | "noImplicitThis": true, 28 | "allowJs": true, 29 | "plugins": [ 30 | { 31 | "name": "typescript-plugin-css-modules" 32 | } 33 | ] 34 | }, 35 | "include": ["./src/**/*.ts", "./prisma/types.d.ts"], 36 | "lib": ["dom"] 37 | } 38 | -------------------------------------------------------------------------------- /settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "trackRegex": [ 3 | "^([\\/\\\\]+.*)*[\\/\\\\]+(?P.+)[\\/\\\\]+Unknown Album[\\/\\\\]+(?P.*)\\..*$", 4 | "^([\\/\\\\]+.*)*[\\/\\\\]+(?P.+)[\\/\\\\]+(?P.+)(\\s+\\((?P\\d{4})\\))?[\\/\\\\]+((?P[0-9]+)-)?(?P[0-9]+)\\s+(?P.*)\\..*$" 5 | ], 6 | "metadata": { 7 | "source": "embedded", 8 | "order": "only", 9 | "useExternalProviderGenres": true 10 | }, 11 | "providers": { 12 | "musicbrainz": {} 13 | }, 14 | "compilations": { 15 | "useID3CompTag": true, 16 | "artists": [ 17 | "Various Artists" 18 | ] 19 | }, 20 | "useEmbeddedThumbnails": false 21 | } --------------------------------------------------------------------------------