├── .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+(?