{"id":31721553,"url":"https://github.com/namelessproj/tunemates_backend","last_synced_at":"2026-04-16T02:32:39.950Z","repository":{"id":316274408,"uuid":"1049732374","full_name":"NamelessProj/TuneMates_Backend","owner":"NamelessProj","description":"A .NET 9 Web API that powers collaborative Spotify playlists. Handles Spotify OAuth, room management, and track proposals, with JWT authentication for owners. Uses PostgreSQL (via Supabase) for persistence. Provides secure endpoints for creating rooms, proposing tracks, and syncing approved songs into Spotify playlists.","archived":false,"fork":false,"pushed_at":"2026-01-13T14:54:13.000Z","size":350,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-01-13T16:34:25.123Z","etag":null,"topics":["api-rest","csharp","dotnet","jwt-authentication","jwt-token","postgresql","postgresql-database","rest-api","spotify","spotify-api","supabase","tunemates"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/NamelessProj.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-03T12:18:45.000Z","updated_at":"2026-01-13T14:54:19.000Z","dependencies_parsed_at":"2026-03-26T00:03:10.536Z","dependency_job_id":null,"html_url":"https://github.com/NamelessProj/TuneMates_Backend","commit_stats":null,"previous_names":["namelessproj/tunemates_backend"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/NamelessProj/TuneMates_Backend","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NamelessProj%2FTuneMates_Backend","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NamelessProj%2FTuneMates_Backend/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NamelessProj%2FTuneMates_Backend/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NamelessProj%2FTuneMates_Backend/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/NamelessProj","download_url":"https://codeload.github.com/NamelessProj/TuneMates_Backend/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NamelessProj%2FTuneMates_Backend/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31868527,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-15T15:24:51.572Z","status":"online","status_checked_at":"2026-04-16T02:00:06.042Z","response_time":69,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-rest","csharp","dotnet","jwt-authentication","jwt-token","postgresql","postgresql-database","rest-api","spotify","spotify-api","supabase","tunemates"],"created_at":"2025-10-09T03:59:30.385Z","updated_at":"2026-04-16T02:32:39.934Z","avatar_url":"https://github.com/NamelessProj.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"﻿![.NET version](https://img.shields.io/badge/.NET_9-512BD4?style=for-the-badge)\n![Project licence](https://img.shields.io/github/license/NamelessProj/TuneMates_Backend?style=for-the-badge)\n![Repo size](https://img.shields.io/github/repo-size/NamelessProj/TuneMates_Backend?style=for-the-badge)\n\n# TuneMates - Backend\nTuneMates is a web application that allows users to create and join music listening rooms, \nwhere they can share and enjoy music together in real-time. The backend is built using .NET 9 in C#.\n\nThis repository contains the backend code for the TuneMates application, which handles user authentication, room management, song proposals, and integration with the Spotify API.\nYou can find the frontend part [here](https://github.com/NamelessProj/TuneMates_Frontend).\n\nThe frontend is hosted on [Vercel](https://tunemates.vercel.app/). For the moment the backend still need to be self-hosted (localhost).\n\n## Features\n- User authentication and authorization\n- Create and join rooms to add songs to a shared Spotify playlist\n- JWT token generation for secure communication\n- Integration with Spotify API for song search and playlist management\n- Background services for cleaning up expired tokens and proposals\n- Database management using Entity Framework Core with Supabase as the backend service\n- Password-protected rooms for added security\n\n## Libraries and Tools\n- [.NET 9](https://dotnet.microsoft.com/en-us/download/dotnet/9.0): The framework used to build the backend.\n- [Entity Framework Core](https://docs.microsoft.com/en-us/ef/core/): ORM for database interactions.\n- [JWT Bearer Authentication](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/configure-jwt-bearer-authentication?view=aspnetcore-9.0): For handling JWT authentication.\n- [Supabase](https://supabase.com/): Backend as a service platform used for database and authentication.\n- [Spotify API](https://developer.spotify.com/documentation/web-api/): For integrating Spotify functionalities.\n\n## Running the Application\n\n### Running with .NET\n1. Clone the repository:\n   ```bash\n   git clone https://github.com/NamelessProj/TuneMates_Backend.git \n   dotnet run\n   ```\n2. Use a tool like Postman to interact with the API endpoints at `https://localhost:7016`.\n\nDon't worry about the dependencies, they will be restored automatically when you run the project.\nSo when you'll run the project for the first time, it may take a bit longer becvause it has to download everything.\nBut after that, next runs will be faster.\n\nIf you don't have .NET 9 installed, you can download it from the [.NET official website](https://dotnet.microsoft.com/en-us/download/dotnet/9.0).\nIt's necessary to run the project locally without Docker.\n\n### Running with Docker\nThe project includes a Dockerfile for containerized deployment with .NET 9.0, ensuring consistent runtime environments across different hosts.\n\n\u003cdetails\u003e\n\n\u003csummary\u003eEverything you need to know\u003c/summary\u003e\n\n#### Configuration Options\nDocker supports three ways to configure the application:\n\n1. **Mount appsettings.json** - Use an existing configuration file\n2. **Use .NET environment variables** - Override specific settings (e.g., `ConnectionStrings__DefaultConnection`)\n3. **Auto-generate from custom environment variables** - The container automatically creates appsettings.json from environment variables if no file is provided\n\n#### Build and Run\n\n1. Build the Docker image:\n   ```bash\n   docker build -t tunemates-backend .\n   ```\n\n2. Run with mounted appsettings.json:\n   ```bash\n   docker run -d \\\n     -p 8080:8080 \\\n     -v $(pwd)/appsettings.json:/app/appsettings.json:ro \\\n     --name tunemates-backend \\\n     tunemates-backend\n   ```\n\n3. Or run with auto-generated appsettings.json from environment variables (requires `CONNECTION_STRING`, `ENCRYPT_KEY`, and `JWT_KEY`):\n   ```bash\n   docker run -d \\\n     -p 8080:8080 \\\n     -e CONNECTION_STRING=\"your_connection_string\" \\\n     -e ENCRYPT_KEY=\"your_base64_key\" \\\n     -e JWT_KEY=\"your_jwt_key\" \\\n     -e JWT_ISSUER=\"TuneMates\" \\\n     -e JWT_AUDIENCE=\"TuneMatesUsers\" \\\n     -e SPOTIFY_CLIENT_ID=\"your_spotify_client_id\" \\\n     -e SPOTIFY_CLIENT_SECRET=\"your_spotify_client_secret\" \\\n     -e SPOTIFY_REDIRECT_URI=\"your_redirect_uri\" \\\n     --name tunemates-backend \\\n     tunemates-backend\n   ```\n   \n   \u003e **Note**: When auto-generating appsettings.json, `CONNECTION_STRING`, `ENCRYPT_KEY`, and `JWT_KEY` are required. The container will fail to start if these are not provided.\n\n4. Access the API at `http://localhost:8080/api`\n\n#### Custom Port Configuration\n\nThe default Docker configuration uses port 8080, but you can customize this using the `ASPNETCORE_URLS` environment variable. For example, to use port 7016 (matching local development):\n\n```bash\ndocker run -d \\\n  -p 7016:7016 \\\n  -e ASPNETCORE_URLS=http://+:7016 \\\n  -v $(pwd)/appsettings.json:/app/appsettings.json:ro \\\n  --name tunemates-backend \\\n  tunemates-backend\n```\n\nAccess the API at `http://localhost:7016/api`\n\nOr modify the `docker-compose.yml` file (update both the ports mapping and environment variable):\n```yaml\n# In the services.backend section:\nports:\n  - \"7016:7016\"  # host:container ports must match\nenvironment:\n  - ASPNETCORE_URLS=http://+:7016  # must match container port above\n```\n\n### Using Docker Compose\nA `docker-compose.yml` file is included in the repository for convenient deployment.\n\n1. Copy the environment template:\n   ```bash\n   cp docker-compose.env.example .env\n   ```\n\n2. Edit `.env` with your configuration values\n\n3. Run:\n   ```bash\n   docker-compose up -d\n   ```\n\nSee [`docker-compose.env.example`](/docker-compose.env.example) for all available environment variables.\n\n\u003c/details\u003e\n\n### Appsettings\nMake sure to configure _(maybe even create)_ your `appsettings.json` file with the necessary settings, such as database connection strings and JWT secret keys.\n\nYou can find a sample configuration in [`appsettings.sample.json`](/appsettings.sample.json).\n\n#### Example\n```json\n{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft.AspNetCore\": \"Warning\"\n    }\n  },\n  \"AllowedHosts\": \"*\",\n  \"ConnectionStrings\": {\n    \"DefaultConnection\": \"supabase connection string here\"\n  },\n  \"Secrets\": {\n    \"EncryptKey64\": \"abcdefghijklmnopqrstuvwxyz1234567890ABCDEFG=\"\n  },\n  \"Jwt\": {\n    \"Key\": \"don't worry about it, this is a good key at least i hope so\",\n    \"Issuer\": \"TuneMates\",\n    \"Audience\": \"TuneMatesUsers\",\n    \"ExpiresInMinutes\": 180 // 3 hours\n  },\n  \"CleanupService\": {\n    \"TokenIntervalHours\": 3,\n    \"ProposalIntervalHours\": 3,\n    \"RoomIntervalHours\": 24,\n    \"SpotifyStateIntervalHours\": 1,\n    \"RoomCodeIntervalHours\": 24\n  },\n  \"Cors\": {\n    \"AllowedOrigins\": [\n      \"http://localhost:5173\",\n      \"https://localhost:5173\"\n    ],\n    \"AllowCredentials\": true\n  },\n  \"RateLimiting\": {\n    \"GlobalPerMinute\": 0, // 0 means disabled\n    \"SearchPerMinute\": 30,\n    \"MutationPerMinute\":  10\n  },\n  \"Spotify\": {\n    \"ClientId\": \"your_spotify_client_id\",\n    \"ClientSecret\": \"your_spotify_client_secret\",\n    \"RedirectUri\": \"your_redirect_uri\"\n  }\n}\n```\n\nThe `ConnectionStrings:DefaultConnection` should be your database connection string.\n\nThe `Secrets:EncryptKey64` should be a base64-encoded string of 64 characters.\n\nThe `Jwt:Key` should be a strong secret key used for signing JWT tokens.\n\nThe `Jwt:Issuer` and `Jwt:Audience` should be set to appropriate values for your application.\n\nThe `CleanupService` section contains settings for background services that clean up expired tokens, proposals, etc. You can adjust the intervals as needed (in hours).\n\nThe `Cors` section contains settings for Cross-Origin Resource Sharing (CORS). You can specify the allowed origins and whether to allow credentials.\n\nThe `RateLimiting` section contains settings for rate limiting API requests. You can specify the number of allowed requests per minute for different categories (global, search, mutation).\n\nThe `Spotify` section should contain your Spotify API credentials.\n\n\u003e[!TIP]\n\u003e ### Spotify Developer Account\n\u003e To use Spotify's API, you need to create a Spotify Developer account and register your application to obtain the `ClientId` and `ClientSecret`. You can do this at [Spotify for Developers](https://developer.spotify.com/dashboard/applications).\n\n## JWT Authentication\nThe application uses JWT (JSON Web Tokens) for authentication.\nAfter a user logs in, they will receive a JWT token that must be included in the `Authorization` header of subsequent requests to protected endpoints.\nThe token should be prefixed with `Bearer `.\n\n#### Example\n```http\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\n```\nThere has to be a space between `Bearer` and the token.\n\n## Database\nThe application uses Entity Framework Core for database management.\n\nThe database schema includes tables for Users, Rooms, Songs, and Tokens.\n\nYou can find the queries to create the necessary tables in the [`database.sql`](/database.sql) file.\n\n\u003e[!WARNING]\n\u003e ### PostgreSQL and Supabase\n\u003e The provided SQL queries are designed for PostgreSQL, which is the database system used by Supabase. If you are using a different database system, you may need to adjust the queries accordingly.\n\n## API Endpoints\n\u003e ✔️: Endpoints that require authentication.\n\u003e\n\u003e ❌: Endpoints that do not require authentication.\n\n### Users\n\n\u003cdetails\u003e\n\n\u003csummary\u003eRoutes\u003c/summary\u003e\n\n| Auth | Method | Endpoint                                                  | Description                                                                         | Body                             | Response                                  |\n|:----:|--------|-----------------------------------------------------------|-------------------------------------------------------------------------------------|----------------------------------|-------------------------------------------|\n|  ❌  | POST   | `/api/users/register`                                     | Register a new user.                                                                | None                            | [User](/DataBase/UserResponse.cs) \u0026 token |\n|  ❌  | POST   | `/api/users/login`                                        | Login a user.                                                                       | None                            | [User](/DataBase/UserResponse.cs) \u0026 token |\n|  ✔️  | POST   | `/api/users/spotify/connect/{code:string}/{state:string}` | Connect the user's Spotify account using the provided authorization code and state. | None                             | [User](/DataBase/UserResponse.cs)         |\n|  ✔️  | POST   | `/api/users/delete/me`                                    | Delete the current user's account.                                                  | None                             | string                                    |\n|  ✔️  | GET    | `/api/users/me`                                           | Get the current user's information.                                                 | None                             | [User](/DataBase/UserResponse.cs)         |\n|  ✔️  | PUT    | `/api/users/me`                                           | Update the current user's information.                                              | [UserDTO](/DataBase/UserDTO.cs)  | [User](/DataBase/UserResponse.cs)         |\n|  ✔️  | PUT    | `/api/users/me/password`                                  | Update the current user's password.                                                 | [UserDTO](/DataBase/UserDTO.cs)  | [User](/DataBase/UserResponse.cs)         |\n\n\u003c/details\u003e\n\n### Rooms\n\n\u003cdetails\u003e\n\n\u003csummary\u003eRoutes\u003c/summary\u003e\n\n| Auth | Method | Endpoint                        | Description                                                                                             | Body                                     | Response                                       |\n|:----:|--------|---------------------------------|---------------------------------------------------------------------------------------------------------|------------------------------------------|------------------------------------------------|\n|  ✔️  | POST   | `/api/rooms`                    | Create a new room.                                                                                      | None                                     | [Room](/DataBase/RoomResponse.cs)              |\n|  ❌  | POST   | `/api/rooms/code`               | Get details of a specific room using a code. _No password needed_                                       | [RoomCode](/DataBase/RoomCode.cs) (code) | [Room](/DataBase/RoomResponse.cs)              |\n|  ✔️  | POST   | `/api/rooms/code/{id:int}`      | Create and get the code to access the room easily. No password required if you have the code of a room. | [RoomCodeDTO](/DataBase/RoomCodeDTO.cs)  | An object with the code and the expiracy date. |\n|  ❌  | POST   | `/api/rooms/slug/{slug:string}` | Get details of a specific room by its slug. _Requires room password_                                    | [RoomDTO](/DataBase/RoomDTO.cs)          | [Room](/DataBase/RoomResponse.cs)              |\n|  ✔️  | POST   | `/api/rooms/delete/code`        | Delete a code.                                                                                          | [RoomCode](/DataBase/RoomCode.cs)        | string                                         |\n|  ✔️  | GET    | `/api/rooms`                    | Get a list of all rooms from the authenticated user.                                                    | None                                     | List of [Room](/DataBase/RoomResponse.cs)      |\n|  ✔️  | GET    | `/api/rooms/{id:int}`           | Get a list of all rooms from the authenticated user.                                                    | None                                     | List of [Room](/DataBase/RoomResponse.cs)      |\n|  ✔️  | GET    | `/api/rooms/codes/{id:int}`     | Get a list of all the codes for the room                                                                | None                                     | List of [RoomCode](/DataBase/RoomCode.cs)      |\n|  ✔️  | PUT    | `/api/rooms/{id:int}`           | Update details of a specific room by its id.                                                            | [RoomDTO](/DataBase/RoomDTO.cs)          | [Room](/DataBase/RoomResponse.cs)              |\n|  ✔️  | PUT    | `/api/rooms/password/{id:int}`  | Update details of a specific room's password by its id.                                                 | [RoomDTO](/DataBase/RoomDTO.cs)          | [Room](/DataBase/RoomResponse.cs)              |\n|  ✔️  | DELETE | `/api/rooms/{id:int}`           | Delete a room by its id.                                                                                | None                                     | string                                         |\n\n\u003c/details\u003e\n\n### Songs\n\n\u003cdetails\u003e\n\n\u003csummary\u003eRoutes\u003c/summary\u003e\n\n| Auth | Method | Endpoint                                           | Description                                                                                                                          | Body                       | Response                          |\n|:----:|--------|----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|----------------------------|-----------------------------------|\n|  ❌  | POST   | `/api/songs/room/{roomId:int}/{songId?:string}`    | Add a new song to a room using a Spotify song ID or the Track URI or URL (in body). The song will be in \"Pending\" status by default. | [Song](/DataBase/Song.cs)? | [Song](/DataBase/Song.cs)         |\n|  ✔️  | GET    | `/api/songs/room/{roomId:int}`                     | Get all songs in a specific room.                                                                                                    | None                       | List of [Song](/DataBase/Song.cs) |\n|  ✔️  | GET    | `/api/songs/room/{roomId:int}/status/{status:int}` | Get songs in a specific room by their status ([SongStatus](/DataBase/SongStatus.cs) Pending: `0`, Approved: `1`, Refused: `2`).      | None                       | List of [Song](/DataBase/Song.cs) |\n\n\u003c/details\u003e\n\n### Spotify\n\n\u003cdetails\u003e\n\n\u003csummary\u003eRoutes\u003c/summary\u003e\n\n| Auth | Method | Endpoint                                                      | Description                                                                                                                                                         | Body  | Response                                        |\n|:----:|--------|---------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------|-------------------------------------------------|\n|  ✔️  | POST   | `/api/spotify/playlist/{roomId:int}/{songId:int}`             | Add a new song directly to the room's Spotify playlist using the songId from the database. The song will be in `Approved` status after being added to the playlist. | None  | [Song](/DataBase/Song.cs) \u0026 snapshot id         |\n|  ✔️  | GET    | `/api/spotify/token`                                          | Get the Spotify access token for the authenticated user.                                                                                                            | None  | string                                          |\n|  ❌  | GET    | `/api/spotify/oathlink`                                       | Get the Spotify OAuth link to authorize the application.                                                                                                            | None  | string                                          |\n|  ✔️  | GET    | `/api/spotify/playlist/me`                                    | Get the authenticated user, all their playlists from Spotify.                                                                                                       | None  | [Playlist Response](/DataBase/SpotifyDTO.cs)    |\n|  ❌  | GET    | `/api/spotify/search/{q:string}/{offset:int}/{market:string}` | Search for songs on Spotify by a query string, with pagination and market specification.                                                                            | None  | [PageResult Response](/DataBase/SpotifyDTO.cs)  |\n\n\u003c/details\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnamelessproj%2Ftunemates_backend","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnamelessproj%2Ftunemates_backend","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnamelessproj%2Ftunemates_backend/lists"}