An open API service indexing awesome lists of open source software.

https://github.com/namelessproj/tunemates_backend

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.
https://github.com/namelessproj/tunemates_backend

api-rest csharp dotnet jwt-authentication jwt-token postgresql postgresql-database rest-api spotify spotify-api supabase tunemates

Last synced: about 2 months ago
JSON representation

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.

Awesome Lists containing this project

README

          

![.NET version](https://img.shields.io/badge/.NET_9-512BD4?style=for-the-badge)
![Project licence](https://img.shields.io/github/license/NamelessProj/TuneMates_Backend?style=for-the-badge)
![Repo size](https://img.shields.io/github/repo-size/NamelessProj/TuneMates_Backend?style=for-the-badge)

# TuneMates - Backend
TuneMates is a web application that allows users to create and join music listening rooms,
where they can share and enjoy music together in real-time. The backend is built using .NET 9 in C#.

This repository contains the backend code for the TuneMates application, which handles user authentication, room management, song proposals, and integration with the Spotify API.
You can find the frontend part [here](https://github.com/NamelessProj/TuneMates_Frontend).

The frontend is hosted on [Vercel](https://tunemates.vercel.app/). For the moment the backend still need to be self-hosted (localhost).

## Features
- User authentication and authorization
- Create and join rooms to add songs to a shared Spotify playlist
- JWT token generation for secure communication
- Integration with Spotify API for song search and playlist management
- Background services for cleaning up expired tokens and proposals
- Database management using Entity Framework Core with Supabase as the backend service
- Password-protected rooms for added security

## Libraries and Tools
- [.NET 9](https://dotnet.microsoft.com/en-us/download/dotnet/9.0): The framework used to build the backend.
- [Entity Framework Core](https://docs.microsoft.com/en-us/ef/core/): ORM for database interactions.
- [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.
- [Supabase](https://supabase.com/): Backend as a service platform used for database and authentication.
- [Spotify API](https://developer.spotify.com/documentation/web-api/): For integrating Spotify functionalities.

## Running the Application

### Running with .NET
1. Clone the repository:
```bash
git clone https://github.com/NamelessProj/TuneMates_Backend.git
dotnet run
```
2. Use a tool like Postman to interact with the API endpoints at `https://localhost:7016`.

Don't worry about the dependencies, they will be restored automatically when you run the project.
So when you'll run the project for the first time, it may take a bit longer becvause it has to download everything.
But after that, next runs will be faster.

If 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).
It's necessary to run the project locally without Docker.

### Running with Docker
The project includes a Dockerfile for containerized deployment with .NET 9.0, ensuring consistent runtime environments across different hosts.

Everything you need to know

#### Configuration Options
Docker supports three ways to configure the application:

1. **Mount appsettings.json** - Use an existing configuration file
2. **Use .NET environment variables** - Override specific settings (e.g., `ConnectionStrings__DefaultConnection`)
3. **Auto-generate from custom environment variables** - The container automatically creates appsettings.json from environment variables if no file is provided

#### Build and Run

1. Build the Docker image:
```bash
docker build -t tunemates-backend .
```

2. Run with mounted appsettings.json:
```bash
docker run -d \
-p 8080:8080 \
-v $(pwd)/appsettings.json:/app/appsettings.json:ro \
--name tunemates-backend \
tunemates-backend
```

3. Or run with auto-generated appsettings.json from environment variables (requires `CONNECTION_STRING`, `ENCRYPT_KEY`, and `JWT_KEY`):
```bash
docker run -d \
-p 8080:8080 \
-e CONNECTION_STRING="your_connection_string" \
-e ENCRYPT_KEY="your_base64_key" \
-e JWT_KEY="your_jwt_key" \
-e JWT_ISSUER="TuneMates" \
-e JWT_AUDIENCE="TuneMatesUsers" \
-e SPOTIFY_CLIENT_ID="your_spotify_client_id" \
-e SPOTIFY_CLIENT_SECRET="your_spotify_client_secret" \
-e SPOTIFY_REDIRECT_URI="your_redirect_uri" \
--name tunemates-backend \
tunemates-backend
```

> **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.

4. Access the API at `http://localhost:8080/api`

#### Custom Port Configuration

The 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):

```bash
docker run -d \
-p 7016:7016 \
-e ASPNETCORE_URLS=http://+:7016 \
-v $(pwd)/appsettings.json:/app/appsettings.json:ro \
--name tunemates-backend \
tunemates-backend
```

Access the API at `http://localhost:7016/api`

Or modify the `docker-compose.yml` file (update both the ports mapping and environment variable):
```yaml
# In the services.backend section:
ports:
- "7016:7016" # host:container ports must match
environment:
- ASPNETCORE_URLS=http://+:7016 # must match container port above
```

### Using Docker Compose
A `docker-compose.yml` file is included in the repository for convenient deployment.

1. Copy the environment template:
```bash
cp docker-compose.env.example .env
```

2. Edit `.env` with your configuration values

3. Run:
```bash
docker-compose up -d
```

See [`docker-compose.env.example`](/docker-compose.env.example) for all available environment variables.

### Appsettings
Make sure to configure _(maybe even create)_ your `appsettings.json` file with the necessary settings, such as database connection strings and JWT secret keys.

You can find a sample configuration in [`appsettings.sample.json`](/appsettings.sample.json).

#### Example
```json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "supabase connection string here"
},
"Secrets": {
"EncryptKey64": "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFG="
},
"Jwt": {
"Key": "don't worry about it, this is a good key at least i hope so",
"Issuer": "TuneMates",
"Audience": "TuneMatesUsers",
"ExpiresInMinutes": 180 // 3 hours
},
"CleanupService": {
"TokenIntervalHours": 3,
"ProposalIntervalHours": 3,
"RoomIntervalHours": 24,
"SpotifyStateIntervalHours": 1,
"RoomCodeIntervalHours": 24
},
"Cors": {
"AllowedOrigins": [
"http://localhost:5173",
"https://localhost:5173"
],
"AllowCredentials": true
},
"RateLimiting": {
"GlobalPerMinute": 0, // 0 means disabled
"SearchPerMinute": 30,
"MutationPerMinute": 10
},
"Spotify": {
"ClientId": "your_spotify_client_id",
"ClientSecret": "your_spotify_client_secret",
"RedirectUri": "your_redirect_uri"
}
}
```

The `ConnectionStrings:DefaultConnection` should be your database connection string.

The `Secrets:EncryptKey64` should be a base64-encoded string of 64 characters.

The `Jwt:Key` should be a strong secret key used for signing JWT tokens.

The `Jwt:Issuer` and `Jwt:Audience` should be set to appropriate values for your application.

The `CleanupService` section contains settings for background services that clean up expired tokens, proposals, etc. You can adjust the intervals as needed (in hours).

The `Cors` section contains settings for Cross-Origin Resource Sharing (CORS). You can specify the allowed origins and whether to allow credentials.

The `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).

The `Spotify` section should contain your Spotify API credentials.

>[!TIP]
> ### Spotify Developer Account
> 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).

## JWT Authentication
The application uses JWT (JSON Web Tokens) for authentication.
After a user logs in, they will receive a JWT token that must be included in the `Authorization` header of subsequent requests to protected endpoints.
The token should be prefixed with `Bearer `.

#### Example
```http
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
There has to be a space between `Bearer` and the token.

## Database
The application uses Entity Framework Core for database management.

The database schema includes tables for Users, Rooms, Songs, and Tokens.

You can find the queries to create the necessary tables in the [`database.sql`](/database.sql) file.

>[!WARNING]
> ### PostgreSQL and Supabase
> 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.

## API Endpoints
> ✔️: Endpoints that require authentication.
>
> ❌: Endpoints that do not require authentication.

### Users

Routes

| Auth | Method | Endpoint | Description | Body | Response |
|:----:|--------|-----------------------------------------------------------|-------------------------------------------------------------------------------------|----------------------------------|-------------------------------------------|
| ❌ | POST | `/api/users/register` | Register a new user. | None | [User](/DataBase/UserResponse.cs) & token |
| ❌ | POST | `/api/users/login` | Login a user. | None | [User](/DataBase/UserResponse.cs) & token |
| ✔️ | 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) |
| ✔️ | POST | `/api/users/delete/me` | Delete the current user's account. | None | string |
| ✔️ | GET | `/api/users/me` | Get the current user's information. | None | [User](/DataBase/UserResponse.cs) |
| ✔️ | PUT | `/api/users/me` | Update the current user's information. | [UserDTO](/DataBase/UserDTO.cs) | [User](/DataBase/UserResponse.cs) |
| ✔️ | PUT | `/api/users/me/password` | Update the current user's password. | [UserDTO](/DataBase/UserDTO.cs) | [User](/DataBase/UserResponse.cs) |

### Rooms

Routes

| Auth | Method | Endpoint | Description | Body | Response |
|:----:|--------|---------------------------------|---------------------------------------------------------------------------------------------------------|------------------------------------------|------------------------------------------------|
| ✔️ | POST | `/api/rooms` | Create a new room. | None | [Room](/DataBase/RoomResponse.cs) |
| ❌ | 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) |
| ✔️ | 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. |
| ❌ | 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) |
| ✔️ | POST | `/api/rooms/delete/code` | Delete a code. | [RoomCode](/DataBase/RoomCode.cs) | string |
| ✔️ | GET | `/api/rooms` | Get a list of all rooms from the authenticated user. | None | List of [Room](/DataBase/RoomResponse.cs) |
| ✔️ | GET | `/api/rooms/{id:int}` | Get a list of all rooms from the authenticated user. | None | List of [Room](/DataBase/RoomResponse.cs) |
| ✔️ | GET | `/api/rooms/codes/{id:int}` | Get a list of all the codes for the room | None | List of [RoomCode](/DataBase/RoomCode.cs) |
| ✔️ | PUT | `/api/rooms/{id:int}` | Update details of a specific room by its id. | [RoomDTO](/DataBase/RoomDTO.cs) | [Room](/DataBase/RoomResponse.cs) |
| ✔️ | 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) |
| ✔️ | DELETE | `/api/rooms/{id:int}` | Delete a room by its id. | None | string |

### Songs

Routes

| Auth | Method | Endpoint | Description | Body | Response |
|:----:|--------|----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|----------------------------|-----------------------------------|
| ❌ | 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) |
| ✔️ | GET | `/api/songs/room/{roomId:int}` | Get all songs in a specific room. | None | List of [Song](/DataBase/Song.cs) |
| ✔️ | 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) |

### Spotify

Routes

| Auth | Method | Endpoint | Description | Body | Response |
|:----:|--------|---------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------|-------------------------------------------------|
| ✔️ | 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) & snapshot id |
| ✔️ | GET | `/api/spotify/token` | Get the Spotify access token for the authenticated user. | None | string |
| ❌ | GET | `/api/spotify/oathlink` | Get the Spotify OAuth link to authorize the application. | None | string |
| ✔️ | GET | `/api/spotify/playlist/me` | Get the authenticated user, all their playlists from Spotify. | None | [Playlist Response](/DataBase/SpotifyDTO.cs) |
| ❌ | 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) |