From 9c373c2672b316350299610cbe031e42fd66e4ce Mon Sep 17 00:00:00 2001 From: Lukas Weger Date: Fri, 1 Aug 2025 18:51:50 +0200 Subject: [PATCH] clean up AI SLOP --- API_DOCUMENTATION.md | 220 ------- backend/API_DOCUMENTATION.md | 578 ++++++++++++++++++ backend/build.gradle | 2 +- backend/settings.gradle | 2 +- .../config/JwtAuthenticationFilter.java | 69 --- .../backend/controller/AuthController.java | 123 ---- .../com/hackathon/backend/model/User.java | 75 --- .../backend/service/SimpleTokenService.java | 85 --- .../backend/service/UserService.java | 60 -- .../backend/BackendApplication.java | 2 +- .../backend/config/SecurityConfig.java | 4 +- .../backend/config/SimpleTokenFilter.java | 16 +- .../backend/controller/ClientController.java | 20 +- .../controller/RadioStationController.java | 29 +- .../backend/controller/SongController.java | 26 +- .../backend/dto/AddSongRequest.java | 2 +- .../backend/dto/ApiResponse.java | 2 +- .../backend/dto/AuthResponse.java | 2 +- .../backend/dto/ConnectClientRequest.java | 2 +- .../dto/CreateRadioStationRequest.java | 2 +- .../backend/dto/LoginRequest.java | 2 +- .../backend/dto/RegisterRequest.java | 2 +- .../dto/UpdateRadioStationRequest.java | 2 +- .../backend/dto/VoteRequest.java | 4 +- .../backend/model/Client.java | 2 +- .../backend/model/RadioStation.java | 2 +- .../backend/model/Song.java | 2 +- .../backend/model/TokenUser.java | 2 +- .../backend/model/VoteType.java | 2 +- .../backend/service/JwtService.java | 30 +- .../backend/service/RadioStationService.java | 10 +- .../backend/util/AuthUtil.java | 4 +- .../backend/BackendApplicationTests.java | 2 +- .../service/RadioStationServiceTest.java | 6 +- 34 files changed, 683 insertions(+), 710 deletions(-) delete mode 100644 API_DOCUMENTATION.md create mode 100644 backend/API_DOCUMENTATION.md delete mode 100644 backend/src/main/java/com/hackathon/backend/config/JwtAuthenticationFilter.java delete mode 100644 backend/src/main/java/com/hackathon/backend/controller/AuthController.java delete mode 100644 backend/src/main/java/com/hackathon/backend/model/User.java delete mode 100644 backend/src/main/java/com/hackathon/backend/service/SimpleTokenService.java delete mode 100644 backend/src/main/java/com/hackathon/backend/service/UserService.java rename backend/src/main/java/com/{hackathon => serena}/backend/BackendApplication.java (90%) rename backend/src/main/java/com/{hackathon => serena}/backend/config/SecurityConfig.java (93%) rename backend/src/main/java/com/{hackathon => serena}/backend/config/SimpleTokenFilter.java (82%) rename backend/src/main/java/com/{hackathon => serena}/backend/controller/ClientController.java (91%) rename backend/src/main/java/com/{hackathon => serena}/backend/controller/RadioStationController.java (89%) rename backend/src/main/java/com/{hackathon => serena}/backend/controller/SongController.java (93%) rename backend/src/main/java/com/{hackathon => serena}/backend/dto/AddSongRequest.java (97%) rename backend/src/main/java/com/{hackathon => serena}/backend/dto/ApiResponse.java (96%) rename backend/src/main/java/com/{hackathon => serena}/backend/dto/AuthResponse.java (96%) rename backend/src/main/java/com/{hackathon => serena}/backend/dto/ConnectClientRequest.java (95%) rename backend/src/main/java/com/{hackathon => serena}/backend/dto/CreateRadioStationRequest.java (93%) rename backend/src/main/java/com/{hackathon => serena}/backend/dto/LoginRequest.java (93%) rename backend/src/main/java/com/{hackathon => serena}/backend/dto/RegisterRequest.java (95%) rename backend/src/main/java/com/{hackathon => serena}/backend/dto/UpdateRadioStationRequest.java (93%) rename backend/src/main/java/com/{hackathon => serena}/backend/dto/VoteRequest.java (86%) rename backend/src/main/java/com/{hackathon => serena}/backend/model/Client.java (97%) rename backend/src/main/java/com/{hackathon => serena}/backend/model/RadioStation.java (98%) rename backend/src/main/java/com/{hackathon => serena}/backend/model/Song.java (98%) rename backend/src/main/java/com/{hackathon => serena}/backend/model/TokenUser.java (95%) rename backend/src/main/java/com/{hackathon => serena}/backend/model/VoteType.java (55%) rename backend/src/main/java/com/{hackathon => serena}/backend/service/JwtService.java (69%) rename backend/src/main/java/com/{hackathon => serena}/backend/service/RadioStationService.java (96%) rename backend/src/main/java/com/{hackathon => serena}/backend/util/AuthUtil.java (92%) rename backend/src/test/java/com/{hackathon => serena}/backend/BackendApplicationTests.java (85%) rename backend/src/test/java/com/{hackathon => serena}/backend/service/RadioStationServiceTest.java (97%) diff --git a/API_DOCUMENTATION.md b/API_DOCUMENTATION.md deleted file mode 100644 index 7e925f1..0000000 --- a/API_DOCUMENTATION.md +++ /dev/null @@ -1,220 +0,0 @@ -# SERENA API - -## Authentication - -This system uses JWT-like tokens generated automatically during key actions: - -- **Creating a radio station** generates an **owner token**. -- **Joining a radio station** generates a **client token**. - -All subsequent requests require this token in the `Authorization` header: - -``` -Authorization: Bearer -``` - -### Token Generation Endpoints - -#### Create Radio Station → Owner Token - -- **POST** `/api/radio-stations` -- **No authentication required** -- **Body**: - -```json -{ - "name": "My Radio Station", - "description": "A cool radio station" -} -``` - -**Response:** - -```json -{ - "success": true, - "message": "Radio station created successfully", - "data": { - "station": { - /* station object */ - }, - "ownerToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." - } -} -``` - -#### Connect to Radio Station → Client Token - -- **POST** `/api/clients/connect` -- **No authentication required** -- **Body Options**: - - - With Station ID + Join Code - - ```json - { - "username": "john_doe", - "radioStationId": "station123", - "joinCode": "ABC123" - } - ``` - - - With Join Code only - - ```json - { "username": "john_doe", "joinCode": "ABC123" } - ``` - -**Response:** - -```json -{ - "success": true, - "message": "Successfully connected to radio station", - "data": { - "client": { - /* client object */ - }, - "clientToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." - } -} -``` - ---- - -## API Endpoints - -### Radio Station Management - -- **Create Station** – `POST /api/radio-stations` (No auth; returns owner token) -- **Get All Stations** – `GET /api/radio-stations` (Auth: owner or client) - - - Optional query param: `activeOnly=true` - -- **Get by ID** – `GET /api/radio-stations/{stationId}` (Auth required) -- **Get by Join Code** – `GET /api/radio-stations/join/{joinCode}` (Public) -- **Update Station** – `PUT /api/radio-stations/{stationId}` (Owner only) -- **Delete Station** – `DELETE /api/radio-stations/{stationId}` (Owner only) - -### Client Management - -- **Connect Client** – `POST /api/clients/connect` (No auth; returns client token) -- **Disconnect Client** – `DELETE /api/clients/{clientId}/disconnect` (Owner only) -- **Get Client Info** – `GET /api/clients/{clientId}` (Auth required) -- **Get Station Clients** – `GET /api/clients/station/{radioStationId}` (Auth required) - -### Song Management - -> All endpoints require a valid **owner** or **client** token unless specified. - -- **Add Song** – `POST /api/radio-stations/{stationId}/songs` -- **Get Queue** – `GET /api/radio-stations/{stationId}/songs/queue` -- **Get Current Song** – `GET /api/radio-stations/{stationId}/songs/current` -- **Play Next Song** – `POST /api/radio-stations/{stationId}/songs/next` (Owner only) -- **Vote on Song** – `POST /api/radio-stations/{stationId}/songs/{songId}/vote` - - ```json - { "voteType": "UPVOTE" } - ``` - - `voteType` can be `"UPVOTE"` or `"DOWNVOTE"`. - -- **Remove Vote** – `DELETE /api/radio-stations/{stationId}/songs/{songId}/vote` - ---- - -## Models - -### RadioStation - -- `id`, `name`, `description`, `ownerId`, `joinCode`, `createdAt`, `isActive`, `connectedClients`, `songQueue`, `currentlyPlaying` - -### Song - -- `id`, `title`, `artist`, `album`, `duration`, `url`, `addedBy`, `addedAt`, `votes`, `upvotes`, `downvotes` - -### Client - -- `id`, `username`, `radioStationId`, `connectedAt`, `isActive` - ---- - -## Authentication & Authorization - -- **Public**: Join stations via join code, connect clients. -- **Authenticated User**: Add/vote on songs, view stations, connect to stations. -- **Station Owner**: Manage station (update/delete), control playback, disconnect clients. - ---- - -## Features - -- JWT-based authentication for most operations. -- Join code–based client connection. -- Owner-only management (updates, deletes, playback). -- Song queue sorted by votes. -- Full CORS support. - ---- - -## Example Flow - -1. User registers/logs in → gets JWT. -2. Owner creates station → gets join code + owner token. -3. Clients join via join code → get client token. -4. Authenticated users add/vote on songs. -5. Owner controls playback & manages station. - ---- - -## Running the Application - -```bash -cd backend -./gradlew bootRun -``` - -Server runs on port `8080`. - -### Quick Auth Test - -```bash -curl -X POST http://localhost:8080/api/auth/register -H "Content-Type: application/json" -d '{"username":"testuser","password":"password123","email":"test@example.com"}' -curl -X POST http://localhost:8080/api/auth/login -H "Content-Type: application/json" -d '{"username":"testuser","password":"password123"}' -curl -X GET http://localhost:8080/api/radio-stations -H "Authorization: Bearer " -``` - ---- - -### **SERENA API Endpoint Table** - -| Method | Endpoint | Description | Auth Required | Notes | -| ---------- | ------------------------------------- | ------------------------ | ------------- | -------------------------- | -| **POST** | `/api/radio-stations` | Create a radio station | ❌ | Returns **owner token** | -| **GET** | `/api/radio-stations` | Get all stations | ✅ | `activeOnly=true` optional | -| **GET** | `/api/radio-stations/{stationId}` | Get station by ID | ✅ | — | -| **GET** | `/api/radio-stations/join/{joinCode}` | Get station by join code | ❌ | Public lookup | -| **PUT** | `/api/radio-stations/{stationId}` | Update station | ✅ (Owner) | Owner only | -| **DELETE** | `/api/radio-stations/{stationId}` | Delete station | ✅ (Owner) | Owner only | - -#### 👥 **Client Management** - -| Method | Endpoint | Description | Auth Required | Notes | -| ---------- | --------------------------------------- | -------------------------- | ------------- | ------------------------ | -| **POST** | `/api/clients/connect` | Connect to station | ❌ | Returns **client token** | -| **DELETE** | `/api/clients/{clientId}/disconnect` | Disconnect client | ✅ (Owner) | Owner only | -| **GET** | `/api/clients/{clientId}` | Get client info | ✅ | — | -| **GET** | `/api/clients/station/{radioStationId}` | Get all clients in station | ✅ | — | - -#### 🎵 **Song Management** - -| Method | Endpoint | Description | Auth Required | Notes | -| ---------- | ----------------------------------------------------- | -------------------------- | ------------- | --------------------------- | -| **POST** | `/api/radio-stations/{stationId}/songs` | Add song to queue | ✅ | Client or Owner | -| **GET** | `/api/radio-stations/{stationId}/songs/queue` | Get song queue | ✅ | Sorted by votes | -| **GET** | `/api/radio-stations/{stationId}/songs/current` | Get currently playing song | ✅ | — | -| **POST** | `/api/radio-stations/{stationId}/songs/next` | Play next song | ✅ (Owner) | Moves top song to “current” | -| **POST** | `/api/radio-stations/{stationId}/songs/{songId}/vote` | Vote on song | ✅ | `voteType`: UPVOTE/DOWNVOTE | -| **DELETE** | `/api/radio-stations/{stationId}/songs/{songId}/vote` | Remove vote | ✅ | Removes user’s vote | - ---- diff --git a/backend/API_DOCUMENTATION.md b/backend/API_DOCUMENTATION.md new file mode 100644 index 0000000..0617740 --- /dev/null +++ b/backend/API_DOCUMENTATION.md @@ -0,0 +1,578 @@ +# Serena API docs + +## Overview + +**Base URL:** `http://localhost:8080/api` + +## Authentication + +The API uses Bearer token authentication. Include the token in the Authorization header: + +``` +Authorization: Bearer +``` + +### Token Types + +- **Owner Token**: Generated when creating a radio station, allows full station management +- **Client Token**: Generated when joining a station, allows participation in the station + +## API Endpoints + +### Radio Station Management + +#### Create Radio Station + +Creates a new radio station and returns an owner token. + +**Endpoint:** `POST /radio-stations` + +**Request Body:** + +```json +{ + "name": "My Awesome Station", + "description": "The best music station ever" +} +``` + +**Response:** + +```json +{ + "success": true, + "message": "Radio station created successfully", + "data": { + "station": { + "id": "station-uuid", + "name": "My Awesome Station", + "description": "The best music station ever", + "ownerId": "owner-uuid", + "joinCode": "ABC123", + "createdAt": "2025-08-01T10:00:00", + "active": true, + "connectedClients": [], + "songQueue": [], + "currentlyPlaying": null + }, + "ownerToken": "jwt-token-here", + "message": "Radio station created successfully. Use this token to manage your station." + } +} +``` + +#### Get All Radio Stations + +Retrieves all radio stations. Requires authentication. + +**Endpoint:** `GET /radio-stations?activeOnly=true` + +**Query Parameters:** + +- `activeOnly` (boolean, default: false): Filter to only active stations + +**Response:** + +```json +{ + "success": true, + "message": "Success", + "data": [ + { + "id": "station-uuid", + "name": "Station Name", + "description": "Station Description", + "ownerId": "owner-uuid", + "joinCode": "ABC123", + "createdAt": "2025-08-01T10:00:00", + "active": true, + "connectedClients": ["client1", "client2"], + "songQueue": [], + "currentlyPlaying": null + } + ] +} +``` + +#### Get Radio Station by ID + +Retrieves a specific radio station. Requires authentication. + +**Endpoint:** `GET /radio-stations/{stationId}` + +**Response:** + +```json +{ + "success": true, + "message": "Success", + "data": { + "id": "station-uuid", + "name": "Station Name", + "description": "Station Description", + "ownerId": "owner-uuid", + "joinCode": "ABC123", + "createdAt": "2025-08-01T10:00:00", + "active": true, + "connectedClients": [], + "songQueue": [], + "currentlyPlaying": null + } +} +``` + +#### Get Radio Station by Join Code + +Retrieves a radio station using its join code. No authentication required. + +**Endpoint:** `GET /radio-stations/join/{joinCode}` + +**Response:** + +```json +{ + "success": true, + "message": "Success", + "data": { + "id": "station-uuid", + "name": "Station Name", + "description": "Station Description", + "ownerId": "owner-uuid", + "joinCode": "ABC123", + "createdAt": "2025-08-01T10:00:00", + "active": true, + "connectedClients": [], + "songQueue": [], + "currentlyPlaying": null + } +} +``` + +#### Update Radio Station + +Updates a radio station. Only the station owner can update it. + +**Endpoint:** `PUT /radio-stations/{stationId}` + +**Request Body:** + +```json +{ + "name": "Updated Station Name", + "description": "Updated description" +} +``` + +**Response:** + +```json +{ + "success": true, + "message": "Radio station updated successfully", + "data": { + "id": "station-uuid", + "name": "Updated Station Name", + "description": "Updated description", + "ownerId": "owner-uuid", + "joinCode": "ABC123", + "createdAt": "2025-08-01T10:00:00", + "active": true, + "connectedClients": [], + "songQueue": [], + "currentlyPlaying": null + } +} +``` + +#### Delete Radio Station + +Deletes a radio station. Only the station owner can delete it. + +**Endpoint:** `DELETE /radio-stations/{stationId}` + +**Response:** + +```json +{ + "success": true, + "message": "Radio station deleted successfully", + "data": null +} +``` + +### Client Management + +#### Connect Client to Station + +Connects a client to a radio station using a join code. No authentication required. + +**Endpoint:** `POST /clients/connect` + +**Request Body (Option 1 - Using station ID and join code):** + +```json +{ + "username": "john_doe", + "radioStationId": "station-uuid", + "joinCode": "ABC123" +} +``` + +**Request Body (Option 2 - Using join code only):** + +```json +{ + "username": "john_doe", + "joinCode": "ABC123" +} +``` + +**Response:** + +```json +{ + "success": true, + "message": "Successfully connected to radio station", + "data": { + "client": { + "id": "client-uuid", + "username": "john_doe", + "radioStationId": "station-uuid", + "connectedAt": "2025-08-01T10:00:00", + "active": true + }, + "clientToken": "jwt-token-here", + "message": "Successfully connected to radio station. Use this token for further requests." + } +} +``` + +#### Disconnect Client + +Disconnects a client from a station. Only the station owner can disconnect clients. + +**Endpoint:** `DELETE /clients/{clientId}/disconnect` + +**Response:** + +```json +{ + "success": true, + "message": "Client disconnected successfully", + "data": null +} +``` + +#### Get Client Information + +Retrieves information about a specific client. Requires authentication. + +**Endpoint:** `GET /clients/{clientId}` + +**Response:** + +```json +{ + "success": true, + "message": "Success", + "data": { + "id": "client-uuid", + "username": "john_doe", + "radioStationId": "station-uuid", + "connectedAt": "2025-08-01T10:00:00", + "active": true + } +} +``` + +#### Get Connected Clients + +Retrieves all clients connected to a radio station. Requires authentication. + +**Endpoint:** `GET /clients/station/{radioStationId}` + +**Response:** + +```json +{ + "success": true, + "message": "Success", + "data": [ + { + "id": "client-uuid", + "username": "john_doe", + "radioStationId": "station-uuid", + "connectedAt": "2025-08-01T10:00:00", + "active": true + } + ] +} +``` + +### Song Management + +#### Add Song to Queue + +Adds a song to the station's queue. Requires authentication. + +**Endpoint:** `POST /radio-stations/{stationId}/songs` + +**Request Body:** + +```json +{ + "title": "Bohemian Rhapsody", + "artist": "Queen", + "album": "A Night at the Opera", + "duration": 355, + "url": "https://example.com/song.mp3" +} +``` + +**Response:** + +```json +{ + "success": true, + "message": "Song added to queue successfully", + "data": { + "id": "song-uuid", + "title": "Bohemian Rhapsody", + "artist": "Queen", + "album": "A Night at the Opera", + "duration": 355, + "url": "https://example.com/song.mp3", + "addedBy": "user-uuid", + "addedAt": "2025-08-01T10:00:00", + "votes": {}, + "upvotes": 0, + "downvotes": 0 + } +} +``` + +#### Get Song Queue + +Retrieves the current song queue, sorted by score (upvotes - downvotes). Requires authentication. + +**Endpoint:** `GET /radio-stations/{stationId}/songs/queue` + +**Response:** + +```json +{ + "success": true, + "message": "Success", + "data": [ + { + "id": "song-uuid", + "title": "Bohemian Rhapsody", + "artist": "Queen", + "album": "A Night at the Opera", + "duration": 355, + "url": "https://example.com/song.mp3", + "addedBy": "user-uuid", + "addedAt": "2025-08-01T10:00:00", + "votes": { + "user1": "UPVOTE", + "user2": "DOWNVOTE" + }, + "upvotes": 1, + "downvotes": 1 + } + ] +} +``` + +#### Get Currently Playing Song + +Retrieves the currently playing song. Requires authentication. + +**Endpoint:** `GET /radio-stations/{stationId}/songs/current` + +**Response:** + +```json +{ + "success": true, + "message": "Success", + "data": { + "id": "song-uuid", + "title": "Bohemian Rhapsody", + "artist": "Queen", + "album": "A Night at the Opera", + "duration": 355, + "url": "https://example.com/song.mp3", + "addedBy": "user-uuid", + "addedAt": "2025-08-01T10:00:00", + "votes": {}, + "upvotes": 0, + "downvotes": 0 + } +} +``` + +#### Play Next Song + +Plays the next song from the queue (highest scoring song). Only station owners can control playback. + +**Endpoint:** `POST /radio-stations/{stationId}/songs/next` + +**Response:** + +```json +{ + "success": true, + "message": "Playing next song", + "data": { + "id": "song-uuid", + "title": "Bohemian Rhapsody", + "artist": "Queen", + "album": "A Night at the Opera", + "duration": 355, + "url": "https://example.com/song.mp3", + "addedBy": "user-uuid", + "addedAt": "2025-08-01T10:00:00", + "votes": {}, + "upvotes": 0, + "downvotes": 0 + } +} +``` + +#### Vote on Song + +Casts a vote (upvote or downvote) on a song. Requires authentication. + +**Endpoint:** `POST /radio-stations/{stationId}/songs/{songId}/vote` + +**Request Body:** + +```json +{ + "voteType": "UPVOTE" +} +``` + +**Vote Types:** + +- `UPVOTE`: Positive vote +- `DOWNVOTE`: Negative vote + +**Response:** + +```json +{ + "success": true, + "message": "Vote recorded successfully", + "data": { + "id": "song-uuid", + "title": "Bohemian Rhapsody", + "artist": "Queen", + "album": "A Night at the Opera", + "duration": 355, + "url": "https://example.com/song.mp3", + "addedBy": "user-uuid", + "addedAt": "2025-08-01T10:00:00", + "votes": { + "current-user-id": "UPVOTE" + }, + "upvotes": 1, + "downvotes": 0 + } +} +``` + +#### Remove Vote from Song + +Removes the current user's vote from a song. Requires authentication. + +**Endpoint:** `DELETE /radio-stations/{stationId}/songs/{songId}/vote` + +**Response:** + +```json +{ + "success": true, + "message": "Vote removed successfully", + "data": null +} +``` + +## Error Responses + +All error responses follow this format: + +```json +{ + "success": false, + "message": "Error description", + "data": null +} +``` + +### Common HTTP Status Codes + +- `200 OK`: Request successful +- `201 Created`: Resource created successfully +- `400 Bad Request`: Invalid request data +- `401 Unauthorized`: Authentication required or token invalid +- `403 Forbidden`: Access denied (e.g., not station owner) +- `404 Not Found`: Resource not found + +### Common Error Messages + +- `"User not authenticated"`: Missing or invalid authentication token +- `"Radio station not found"`: Station ID or join code not found +- `"Only the station owner can..."`: Action requires owner privileges +- `"Failed to connect to radio station. Invalid join code or station not found."`: Join code is invalid +- `"Client not found"`: Client ID not found + +## Usage Examples + +### Creating a Station and Adding Songs + +1. **Create a station:** + +```bash +curl -X POST http://localhost:8080/api/radio-stations \ + -H "Content-Type: application/json" \ + -d '{"name": "My Station", "description": "Great music"}' +``` + +2. **Use the returned owner token for authenticated requests:** + +```bash +curl -X POST http://localhost:8080/api/radio-stations/{stationId}/songs \ + -H "Authorization: Bearer {ownerToken}" \ + -H "Content-Type: application/json" \ + -d '{"title": "Song Title", "artist": "Artist", "duration": 180, "url": "http://example.com/song.mp3"}' +``` + +### Joining a Station and Voting + +1. **Join a station:** + +```bash +curl -X POST http://localhost:8080/api/clients/connect \ + -H "Content-Type: application/json" \ + -d '{"username": "john", "joinCode": "ABC123"}' +``` + +2. **Vote on a song:** + +```bash +curl -X POST http://localhost:8080/api/radio-stations/{stationId}/songs/{songId}/vote \ + -H "Authorization: Bearer {clientToken}" \ + -H "Content-Type: application/json" \ + -d '{"voteType": "UPVOTE"}' +``` + +## Notes + +- Join codes are 6-character alphanumeric strings automatically generated for each station +- Songs in the queue are sorted by score (upvotes - downvotes) in descending order +- Users can change their vote on a song, but only have one vote per song +- Only station owners can control playback (play next song) +- Tokens expire after 7 days +- The system uses in-memory storage, so data is lost when the server restarts diff --git a/backend/build.gradle b/backend/build.gradle index b670cd6..b098bfa 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -4,7 +4,7 @@ plugins { id 'io.spring.dependency-management' version '1.1.7' } -group = 'com.hackathon' +group = 'com.serena' version = '0.0.1-SNAPSHOT' java { diff --git a/backend/settings.gradle b/backend/settings.gradle index 0f5036d..1929111 100644 --- a/backend/settings.gradle +++ b/backend/settings.gradle @@ -1 +1 @@ -rootProject.name = 'backend' +rootProject.name = 'serena_backend' diff --git a/backend/src/main/java/com/hackathon/backend/config/JwtAuthenticationFilter.java b/backend/src/main/java/com/hackathon/backend/config/JwtAuthenticationFilter.java deleted file mode 100644 index 733564f..0000000 --- a/backend/src/main/java/com/hackathon/backend/config/JwtAuthenticationFilter.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.hackathon.backend.config; - -import com.hackathon.backend.service.JwtService; -import com.hackathon.backend.service.UserService; -import com.hackathon.backend.model.User; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.lang.NonNull; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Optional; - -@Component -public class JwtAuthenticationFilter extends OncePerRequestFilter { - - @Autowired - private JwtService jwtService; - - @Autowired - private UserService userService; - - @Override - protected void doFilterInternal( - @NonNull HttpServletRequest request, - @NonNull HttpServletResponse response, - @NonNull FilterChain filterChain) throws ServletException, IOException { - - final String authHeader = request.getHeader("Authorization"); - final String jwt; - final String userId; - - if (authHeader == null || !authHeader.startsWith("Bearer ")) { - filterChain.doFilter(request, response); - return; - } - - jwt = authHeader.substring(7); - userId = jwtService.extractUserId(jwt); - - if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) { - Optional userOpt = userService.getUserById(userId); - - if (userOpt.isPresent()) { - User user = userOpt.get(); - - if (jwtService.isTokenValid(jwt, userId) && user.isActive()) { - UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( - user, - null, - new ArrayList<>()); - authToken.setDetails( - new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authToken); - } - } - } - - filterChain.doFilter(request, response); - } -} diff --git a/backend/src/main/java/com/hackathon/backend/controller/AuthController.java b/backend/src/main/java/com/hackathon/backend/controller/AuthController.java deleted file mode 100644 index e3647cd..0000000 --- a/backend/src/main/java/com/hackathon/backend/controller/AuthController.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.hackathon.backend.controller; - -import com.hackathon.backend.dto.ApiResponse; -import com.hackathon.backend.dto.AuthResponse; -import com.hackathon.backend.dto.LoginRequest; -import com.hackathon.backend.dto.RegisterRequest; -import com.hackathon.backend.model.User; -import com.hackathon.backend.service.JwtService; -import com.hackathon.backend.service.UserService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.Optional; - -@RestController -@RequestMapping("/api/auth") -@CrossOrigin(origins = "*") -public class AuthController { - - @Autowired - private UserService userService; - - @Autowired - private JwtService jwtService; - - @PostMapping("/register") - public ResponseEntity> register(@RequestBody RegisterRequest request) { - if (request.getUsername() == null || request.getUsername().trim().isEmpty()) { - return ResponseEntity.badRequest() - .body(ApiResponse.error("Username is required")); - } - - if (request.getPassword() == null || request.getPassword().length() < 6) { - return ResponseEntity.badRequest() - .body(ApiResponse.error("Password must be at least 6 characters long")); - } - - if (request.getEmail() == null || request.getEmail().trim().isEmpty()) { - return ResponseEntity.badRequest() - .body(ApiResponse.error("Email is required")); - } - - Optional userOpt = userService.registerUser( - request.getUsername().trim(), - request.getPassword(), - request.getEmail().trim()); - - if (userOpt.isEmpty()) { - return ResponseEntity.status(HttpStatus.CONFLICT) - .body(ApiResponse.error("Username already exists")); - } - - User user = userOpt.get(); - String token = jwtService.generateToken(user.getId(), user.getUsername()); - - AuthResponse authResponse = new AuthResponse( - token, - user.getId(), - user.getUsername(), - jwtService.getExpirationTime()); - - return ResponseEntity.status(HttpStatus.CREATED) - .body(ApiResponse.success("User registered successfully", authResponse)); - } - - @PostMapping("/login") - public ResponseEntity> login(@RequestBody LoginRequest request) { - if (request.getUsername() == null || request.getUsername().trim().isEmpty()) { - return ResponseEntity.badRequest() - .body(ApiResponse.error("Username is required")); - } - - if (request.getPassword() == null || request.getPassword().isEmpty()) { - return ResponseEntity.badRequest() - .body(ApiResponse.error("Password is required")); - } - - Optional userOpt = userService.authenticateUser( - request.getUsername().trim(), - request.getPassword()); - - if (userOpt.isEmpty()) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(ApiResponse.error("Invalid username or password")); - } - - User user = userOpt.get(); - String token = jwtService.generateToken(user.getId(), user.getUsername()); - - AuthResponse authResponse = new AuthResponse( - token, - user.getId(), - user.getUsername(), - jwtService.getExpirationTime()); - - return ResponseEntity.ok(ApiResponse.success("Login successful", authResponse)); - } - - @GetMapping("/me") - public ResponseEntity> getCurrentUser(@RequestHeader("Authorization") String authHeader) { - try { - String token = authHeader.substring(7); // Remove "Bearer " - String userId = jwtService.extractUserId(token); - - Optional userOpt = userService.getUserById(userId); - if (userOpt.isEmpty()) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(ApiResponse.error("User not found")); - } - - User user = userOpt.get(); - // Don't send password in response - user.setPassword(null); - - return ResponseEntity.ok(ApiResponse.success(user)); - } catch (Exception e) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(ApiResponse.error("Invalid token")); - } - } -} diff --git a/backend/src/main/java/com/hackathon/backend/model/User.java b/backend/src/main/java/com/hackathon/backend/model/User.java deleted file mode 100644 index e406fad..0000000 --- a/backend/src/main/java/com/hackathon/backend/model/User.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.hackathon.backend.model; - -import java.time.LocalDateTime; -import java.util.UUID; - -public class User { - private String id; - private String username; - private String password; // This would be hashed in a real application - private String email; - private LocalDateTime createdAt; - private boolean isActive; - - public User() { - this.id = UUID.randomUUID().toString(); - this.createdAt = LocalDateTime.now(); - this.isActive = true; - } - - public User(String username, String password, String email) { - this(); - this.username = username; - this.password = password; - this.email = email; - } - - // Getters and Setters - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public boolean isActive() { - return isActive; - } - - public void setActive(boolean active) { - isActive = active; - } -} diff --git a/backend/src/main/java/com/hackathon/backend/service/SimpleTokenService.java b/backend/src/main/java/com/hackathon/backend/service/SimpleTokenService.java deleted file mode 100644 index f976565..0000000 --- a/backend/src/main/java/com/hackathon/backend/service/SimpleTokenService.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.hackathon.backend.service; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; -import org.springframework.stereotype.Service; - -import javax.crypto.SecretKey; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.function.Function; - -@Service -public class SimpleTokenService { - - private static final String SECRET_KEY = "404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970"; - private static final long TOKEN_EXPIRATION = 7 * 24 * 60 * 60 * 1000; // 7 days - - public String generateToken(String username, String role) { - String userId = UUID.randomUUID().toString(); - Map claims = new HashMap<>(); - claims.put("username", username); - claims.put("role", role); // "owner" or "client" - - return Jwts.builder() - .claims(claims) - .subject(userId) - .issuedAt(new Date(System.currentTimeMillis())) - .expiration(new Date(System.currentTimeMillis() + TOKEN_EXPIRATION)) - .signWith(getSignInKey()) - .compact(); - } - - public String extractUserId(String token) { - return extractClaim(token, Claims::getSubject); - } - - public String extractUsername(String token) { - return extractClaim(token, claims -> claims.get("username", String.class)); - } - - public String extractRole(String token) { - return extractClaim(token, claims -> claims.get("role", String.class)); - } - - public T extractClaim(String token, Function claimsResolver) { - final Claims claims = extractAllClaims(token); - return claimsResolver.apply(claims); - } - - public boolean isTokenValid(String token) { - try { - return !isTokenExpired(token); - } catch (Exception e) { - return false; - } - } - - private boolean isTokenExpired(String token) { - return extractExpiration(token).before(new Date()); - } - - private Date extractExpiration(String token) { - return extractClaim(token, Claims::getExpiration); - } - - private Claims extractAllClaims(String token) { - return Jwts.parser() - .verifyWith(getSignInKey()) - .build() - .parseSignedClaims(token) - .getPayload(); - } - - private SecretKey getSignInKey() { - byte[] keyBytes = SECRET_KEY.getBytes(); - return Keys.hmacShaKeyFor(keyBytes); - } - - public long getExpirationTime() { - return TOKEN_EXPIRATION; - } -} diff --git a/backend/src/main/java/com/hackathon/backend/service/UserService.java b/backend/src/main/java/com/hackathon/backend/service/UserService.java deleted file mode 100644 index 7f0811c..0000000 --- a/backend/src/main/java/com/hackathon/backend/service/UserService.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.hackathon.backend.service; - -import com.hackathon.backend.model.User; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -@Service -public class UserService { - - private final Map users = new HashMap<>(); - private final Map usersByUsername = new HashMap<>(); - - @Autowired - private PasswordEncoder passwordEncoder; - - public Optional registerUser(String username, String password, String email) { - // Check if username already exists - if (usersByUsername.containsKey(username)) { - return Optional.empty(); - } - - // Hash the password - String hashedPassword = passwordEncoder.encode(password); - User user = new User(username, hashedPassword, email); - users.put(user.getId(), user); - usersByUsername.put(username, user.getId()); - - return Optional.of(user); - } - - public Optional authenticateUser(String username, String password) { - String userId = usersByUsername.get(username); - if (userId != null) { - User user = users.get(userId); - // Compare hashed passwords - if (user != null && passwordEncoder.matches(password, user.getPassword()) && user.isActive()) { - return Optional.of(user); - } - } - return Optional.empty(); - } - - public Optional getUserById(String userId) { - return Optional.ofNullable(users.get(userId)); - } - - public Optional getUserByUsername(String username) { - String userId = usersByUsername.get(username); - return userId != null ? Optional.ofNullable(users.get(userId)) : Optional.empty(); - } - - public boolean userExists(String username) { - return usersByUsername.containsKey(username); - } -} diff --git a/backend/src/main/java/com/hackathon/backend/BackendApplication.java b/backend/src/main/java/com/serena/backend/BackendApplication.java similarity index 90% rename from backend/src/main/java/com/hackathon/backend/BackendApplication.java rename to backend/src/main/java/com/serena/backend/BackendApplication.java index b50ad92..238fdf2 100644 --- a/backend/src/main/java/com/hackathon/backend/BackendApplication.java +++ b/backend/src/main/java/com/serena/backend/BackendApplication.java @@ -1,4 +1,4 @@ -package com.hackathon.backend; +package com.serena.backend; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/backend/src/main/java/com/hackathon/backend/config/SecurityConfig.java b/backend/src/main/java/com/serena/backend/config/SecurityConfig.java similarity index 93% rename from backend/src/main/java/com/hackathon/backend/config/SecurityConfig.java rename to backend/src/main/java/com/serena/backend/config/SecurityConfig.java index 42f6773..d9d8b04 100644 --- a/backend/src/main/java/com/hackathon/backend/config/SecurityConfig.java +++ b/backend/src/main/java/com/serena/backend/config/SecurityConfig.java @@ -1,4 +1,4 @@ -package com.hackathon.backend.config; +package com.serena.backend.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -6,8 +6,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; diff --git a/backend/src/main/java/com/hackathon/backend/config/SimpleTokenFilter.java b/backend/src/main/java/com/serena/backend/config/SimpleTokenFilter.java similarity index 82% rename from backend/src/main/java/com/hackathon/backend/config/SimpleTokenFilter.java rename to backend/src/main/java/com/serena/backend/config/SimpleTokenFilter.java index 2db1d63..583f8ae 100644 --- a/backend/src/main/java/com/hackathon/backend/config/SimpleTokenFilter.java +++ b/backend/src/main/java/com/serena/backend/config/SimpleTokenFilter.java @@ -1,7 +1,7 @@ -package com.hackathon.backend.config; +package com.serena.backend.config; -import com.hackathon.backend.service.SimpleTokenService; -import com.hackathon.backend.model.TokenUser; +import com.serena.backend.service.JwtService; +import com.serena.backend.model.TokenUser; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -21,7 +21,7 @@ import java.util.ArrayList; public class SimpleTokenFilter extends OncePerRequestFilter { @Autowired - private SimpleTokenService tokenService; + private JwtService jwtService; @Override protected void doFilterInternal( @@ -41,10 +41,10 @@ public class SimpleTokenFilter extends OncePerRequestFilter { if (SecurityContextHolder.getContext().getAuthentication() == null) { try { - if (tokenService.isTokenValid(token)) { - String userId = tokenService.extractUserId(token); - String username = tokenService.extractUsername(token); - String role = tokenService.extractRole(token); + if (jwtService.isTokenValid(token)) { + String userId = jwtService.extractUserId(token); + String username = jwtService.extractUsername(token); + String role = jwtService.extractRole(token); TokenUser tokenUser = new TokenUser(userId, username, role); diff --git a/backend/src/main/java/com/hackathon/backend/controller/ClientController.java b/backend/src/main/java/com/serena/backend/controller/ClientController.java similarity index 91% rename from backend/src/main/java/com/hackathon/backend/controller/ClientController.java rename to backend/src/main/java/com/serena/backend/controller/ClientController.java index b6d6be2..00b718e 100644 --- a/backend/src/main/java/com/hackathon/backend/controller/ClientController.java +++ b/backend/src/main/java/com/serena/backend/controller/ClientController.java @@ -1,12 +1,12 @@ -package com.hackathon.backend.controller; +package com.serena.backend.controller; -import com.hackathon.backend.dto.ApiResponse; -import com.hackathon.backend.dto.ConnectClientRequest; -import com.hackathon.backend.model.Client; -import com.hackathon.backend.model.RadioStation; -import com.hackathon.backend.service.RadioStationService; -import com.hackathon.backend.service.SimpleTokenService; -import com.hackathon.backend.util.AuthUtil; +import com.serena.backend.dto.ApiResponse; +import com.serena.backend.dto.ConnectClientRequest; +import com.serena.backend.model.Client; +import com.serena.backend.model.RadioStation; +import com.serena.backend.service.RadioStationService; +import com.serena.backend.service.JwtService; +import com.serena.backend.util.AuthUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -26,7 +26,7 @@ public class ClientController { private RadioStationService radioStationService; @Autowired - private SimpleTokenService tokenService; + private JwtService jwtService; @PostMapping("/connect") public ResponseEntity>> connectClient(@RequestBody ConnectClientRequest request) { @@ -48,7 +48,7 @@ public class ClientController { if (client.isPresent()) { // Generate a client token - String clientToken = tokenService.generateToken(request.getUsername(), "client"); + String clientToken = jwtService.generateTokenWithRole(request.getUsername(), "client"); Map response = new HashMap<>(); response.put("client", client.get()); diff --git a/backend/src/main/java/com/hackathon/backend/controller/RadioStationController.java b/backend/src/main/java/com/serena/backend/controller/RadioStationController.java similarity index 89% rename from backend/src/main/java/com/hackathon/backend/controller/RadioStationController.java rename to backend/src/main/java/com/serena/backend/controller/RadioStationController.java index c6a6e5e..b2afb56 100644 --- a/backend/src/main/java/com/hackathon/backend/controller/RadioStationController.java +++ b/backend/src/main/java/com/serena/backend/controller/RadioStationController.java @@ -1,12 +1,12 @@ -package com.hackathon.backend.controller; +package com.serena.backend.controller; -import com.hackathon.backend.dto.ApiResponse; -import com.hackathon.backend.dto.CreateRadioStationRequest; -import com.hackathon.backend.dto.UpdateRadioStationRequest; -import com.hackathon.backend.model.RadioStation; -import com.hackathon.backend.service.RadioStationService; -import com.hackathon.backend.service.SimpleTokenService; -import com.hackathon.backend.util.AuthUtil; +import com.serena.backend.dto.ApiResponse; +import com.serena.backend.dto.CreateRadioStationRequest; +import com.serena.backend.dto.UpdateRadioStationRequest; +import com.serena.backend.model.RadioStation; +import com.serena.backend.service.RadioStationService; +import com.serena.backend.service.JwtService; +import com.serena.backend.util.AuthUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -26,14 +26,15 @@ public class RadioStationController { private RadioStationService radioStationService; @Autowired - private SimpleTokenService tokenService; + private JwtService jwtService; @PostMapping - public ResponseEntity>> createRadioStation(@RequestBody CreateRadioStationRequest request) { + public ResponseEntity>> createRadioStation( + @RequestBody CreateRadioStationRequest request) { try { // Generate a token for the station owner - String ownerToken = tokenService.generateToken(request.getName() + "_owner", "owner"); - String ownerId = tokenService.extractUserId(ownerToken); + String ownerToken = jwtService.generateTokenWithRole(request.getName() + "_owner", "owner"); + String ownerId = jwtService.extractUserId(ownerToken); RadioStation station = radioStationService.createRadioStation( request.getName(), @@ -101,7 +102,7 @@ public class RadioStationController { public ResponseEntity> updateRadioStation( @PathVariable String stationId, @RequestBody UpdateRadioStationRequest request) { - + String currentUserId = AuthUtil.getCurrentUserId(); if (currentUserId == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) @@ -127,7 +128,7 @@ public class RadioStationController { stationId, request.getName(), request.getDescription()); - + if (updated.isPresent()) { return ResponseEntity.ok(ApiResponse.success("Radio station updated successfully", updated.get())); } else { diff --git a/backend/src/main/java/com/hackathon/backend/controller/SongController.java b/backend/src/main/java/com/serena/backend/controller/SongController.java similarity index 93% rename from backend/src/main/java/com/hackathon/backend/controller/SongController.java rename to backend/src/main/java/com/serena/backend/controller/SongController.java index 2923819..f354295 100644 --- a/backend/src/main/java/com/hackathon/backend/controller/SongController.java +++ b/backend/src/main/java/com/serena/backend/controller/SongController.java @@ -1,12 +1,12 @@ -package com.hackathon.backend.controller; +package com.serena.backend.controller; -import com.hackathon.backend.dto.AddSongRequest; -import com.hackathon.backend.dto.ApiResponse; -import com.hackathon.backend.dto.VoteRequest; -import com.hackathon.backend.model.RadioStation; -import com.hackathon.backend.model.Song; -import com.hackathon.backend.service.RadioStationService; -import com.hackathon.backend.util.AuthUtil; +import com.serena.backend.dto.AddSongRequest; +import com.serena.backend.dto.ApiResponse; +import com.serena.backend.dto.VoteRequest; +import com.serena.backend.model.RadioStation; +import com.serena.backend.model.Song; +import com.serena.backend.service.RadioStationService; +import com.serena.backend.util.AuthUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -27,7 +27,7 @@ public class SongController { public ResponseEntity> addSongToQueue( @PathVariable String stationId, @RequestBody AddSongRequest request) { - + String currentUserId = AuthUtil.getCurrentUserId(); if (currentUserId == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) @@ -50,7 +50,7 @@ public class SongController { request.getDuration(), request.getUrl(), currentUserId); - + if (song.isPresent()) { return ResponseEntity.status(HttpStatus.CREATED) .body(ApiResponse.success("Song added to queue successfully", song.get())); @@ -124,7 +124,7 @@ public class SongController { @PathVariable String stationId, @PathVariable String songId, @RequestBody VoteRequest request) { - + String currentUserId = AuthUtil.getCurrentUserId(); if (currentUserId == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) @@ -137,7 +137,7 @@ public class SongController { songId, currentUserId, request.getVoteType()); - + if (song.isPresent()) { return ResponseEntity.ok(ApiResponse.success("Vote recorded successfully", song.get())); } else { @@ -150,7 +150,7 @@ public class SongController { public ResponseEntity> removeSongVote( @PathVariable String stationId, @PathVariable String songId) { - + String currentUserId = AuthUtil.getCurrentUserId(); if (currentUserId == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) diff --git a/backend/src/main/java/com/hackathon/backend/dto/AddSongRequest.java b/backend/src/main/java/com/serena/backend/dto/AddSongRequest.java similarity index 97% rename from backend/src/main/java/com/hackathon/backend/dto/AddSongRequest.java rename to backend/src/main/java/com/serena/backend/dto/AddSongRequest.java index c925d2a..f186b68 100644 --- a/backend/src/main/java/com/hackathon/backend/dto/AddSongRequest.java +++ b/backend/src/main/java/com/serena/backend/dto/AddSongRequest.java @@ -1,4 +1,4 @@ -package com.hackathon.backend.dto; +package com.serena.backend.dto; public class AddSongRequest { private String title; diff --git a/backend/src/main/java/com/hackathon/backend/dto/ApiResponse.java b/backend/src/main/java/com/serena/backend/dto/ApiResponse.java similarity index 96% rename from backend/src/main/java/com/hackathon/backend/dto/ApiResponse.java rename to backend/src/main/java/com/serena/backend/dto/ApiResponse.java index d58dbad..679d839 100644 --- a/backend/src/main/java/com/hackathon/backend/dto/ApiResponse.java +++ b/backend/src/main/java/com/serena/backend/dto/ApiResponse.java @@ -1,4 +1,4 @@ -package com.hackathon.backend.dto; +package com.serena.backend.dto; public class ApiResponse { private boolean success; diff --git a/backend/src/main/java/com/hackathon/backend/dto/AuthResponse.java b/backend/src/main/java/com/serena/backend/dto/AuthResponse.java similarity index 96% rename from backend/src/main/java/com/hackathon/backend/dto/AuthResponse.java rename to backend/src/main/java/com/serena/backend/dto/AuthResponse.java index c8357be..8a3adad 100644 --- a/backend/src/main/java/com/hackathon/backend/dto/AuthResponse.java +++ b/backend/src/main/java/com/serena/backend/dto/AuthResponse.java @@ -1,4 +1,4 @@ -package com.hackathon.backend.dto; +package com.serena.backend.dto; public class AuthResponse { private String token; diff --git a/backend/src/main/java/com/hackathon/backend/dto/ConnectClientRequest.java b/backend/src/main/java/com/serena/backend/dto/ConnectClientRequest.java similarity index 95% rename from backend/src/main/java/com/hackathon/backend/dto/ConnectClientRequest.java rename to backend/src/main/java/com/serena/backend/dto/ConnectClientRequest.java index 106f07b..8e5ad9b 100644 --- a/backend/src/main/java/com/hackathon/backend/dto/ConnectClientRequest.java +++ b/backend/src/main/java/com/serena/backend/dto/ConnectClientRequest.java @@ -1,4 +1,4 @@ -package com.hackathon.backend.dto; +package com.serena.backend.dto; public class ConnectClientRequest { private String username; diff --git a/backend/src/main/java/com/hackathon/backend/dto/CreateRadioStationRequest.java b/backend/src/main/java/com/serena/backend/dto/CreateRadioStationRequest.java similarity index 93% rename from backend/src/main/java/com/hackathon/backend/dto/CreateRadioStationRequest.java rename to backend/src/main/java/com/serena/backend/dto/CreateRadioStationRequest.java index e7afac9..946b3fa 100644 --- a/backend/src/main/java/com/hackathon/backend/dto/CreateRadioStationRequest.java +++ b/backend/src/main/java/com/serena/backend/dto/CreateRadioStationRequest.java @@ -1,4 +1,4 @@ -package com.hackathon.backend.dto; +package com.serena.backend.dto; public class CreateRadioStationRequest { private String name; diff --git a/backend/src/main/java/com/hackathon/backend/dto/LoginRequest.java b/backend/src/main/java/com/serena/backend/dto/LoginRequest.java similarity index 93% rename from backend/src/main/java/com/hackathon/backend/dto/LoginRequest.java rename to backend/src/main/java/com/serena/backend/dto/LoginRequest.java index b5cfd4b..56f27fc 100644 --- a/backend/src/main/java/com/hackathon/backend/dto/LoginRequest.java +++ b/backend/src/main/java/com/serena/backend/dto/LoginRequest.java @@ -1,4 +1,4 @@ -package com.hackathon.backend.dto; +package com.serena.backend.dto; public class LoginRequest { private String username; diff --git a/backend/src/main/java/com/hackathon/backend/dto/RegisterRequest.java b/backend/src/main/java/com/serena/backend/dto/RegisterRequest.java similarity index 95% rename from backend/src/main/java/com/hackathon/backend/dto/RegisterRequest.java rename to backend/src/main/java/com/serena/backend/dto/RegisterRequest.java index ce65776..f6d2ac4 100644 --- a/backend/src/main/java/com/hackathon/backend/dto/RegisterRequest.java +++ b/backend/src/main/java/com/serena/backend/dto/RegisterRequest.java @@ -1,4 +1,4 @@ -package com.hackathon.backend.dto; +package com.serena.backend.dto; public class RegisterRequest { private String username; diff --git a/backend/src/main/java/com/hackathon/backend/dto/UpdateRadioStationRequest.java b/backend/src/main/java/com/serena/backend/dto/UpdateRadioStationRequest.java similarity index 93% rename from backend/src/main/java/com/hackathon/backend/dto/UpdateRadioStationRequest.java rename to backend/src/main/java/com/serena/backend/dto/UpdateRadioStationRequest.java index 967d142..9057eea 100644 --- a/backend/src/main/java/com/hackathon/backend/dto/UpdateRadioStationRequest.java +++ b/backend/src/main/java/com/serena/backend/dto/UpdateRadioStationRequest.java @@ -1,4 +1,4 @@ -package com.hackathon.backend.dto; +package com.serena.backend.dto; public class UpdateRadioStationRequest { private String name; diff --git a/backend/src/main/java/com/hackathon/backend/dto/VoteRequest.java b/backend/src/main/java/com/serena/backend/dto/VoteRequest.java similarity index 86% rename from backend/src/main/java/com/hackathon/backend/dto/VoteRequest.java rename to backend/src/main/java/com/serena/backend/dto/VoteRequest.java index a236b66..c1a03ba 100644 --- a/backend/src/main/java/com/hackathon/backend/dto/VoteRequest.java +++ b/backend/src/main/java/com/serena/backend/dto/VoteRequest.java @@ -1,6 +1,6 @@ -package com.hackathon.backend.dto; +package com.serena.backend.dto; -import com.hackathon.backend.model.VoteType; +import com.serena.backend.model.VoteType; public class VoteRequest { private String clientId; diff --git a/backend/src/main/java/com/hackathon/backend/model/Client.java b/backend/src/main/java/com/serena/backend/model/Client.java similarity index 97% rename from backend/src/main/java/com/hackathon/backend/model/Client.java rename to backend/src/main/java/com/serena/backend/model/Client.java index 734e7d7..4427590 100644 --- a/backend/src/main/java/com/hackathon/backend/model/Client.java +++ b/backend/src/main/java/com/serena/backend/model/Client.java @@ -1,4 +1,4 @@ -package com.hackathon.backend.model; +package com.serena.backend.model; import java.time.LocalDateTime; import java.util.UUID; diff --git a/backend/src/main/java/com/hackathon/backend/model/RadioStation.java b/backend/src/main/java/com/serena/backend/model/RadioStation.java similarity index 98% rename from backend/src/main/java/com/hackathon/backend/model/RadioStation.java rename to backend/src/main/java/com/serena/backend/model/RadioStation.java index 9cfec77..eadda34 100644 --- a/backend/src/main/java/com/hackathon/backend/model/RadioStation.java +++ b/backend/src/main/java/com/serena/backend/model/RadioStation.java @@ -1,4 +1,4 @@ -package com.hackathon.backend.model; +package com.serena.backend.model; import java.time.LocalDateTime; import java.util.ArrayList; diff --git a/backend/src/main/java/com/hackathon/backend/model/Song.java b/backend/src/main/java/com/serena/backend/model/Song.java similarity index 98% rename from backend/src/main/java/com/hackathon/backend/model/Song.java rename to backend/src/main/java/com/serena/backend/model/Song.java index 6504dcf..4f85d3b 100644 --- a/backend/src/main/java/com/hackathon/backend/model/Song.java +++ b/backend/src/main/java/com/serena/backend/model/Song.java @@ -1,4 +1,4 @@ -package com.hackathon.backend.model; +package com.serena.backend.model; import java.time.LocalDateTime; import java.util.HashMap; diff --git a/backend/src/main/java/com/hackathon/backend/model/TokenUser.java b/backend/src/main/java/com/serena/backend/model/TokenUser.java similarity index 95% rename from backend/src/main/java/com/hackathon/backend/model/TokenUser.java rename to backend/src/main/java/com/serena/backend/model/TokenUser.java index 69db0a6..7e030a7 100644 --- a/backend/src/main/java/com/hackathon/backend/model/TokenUser.java +++ b/backend/src/main/java/com/serena/backend/model/TokenUser.java @@ -1,4 +1,4 @@ -package com.hackathon.backend.model; +package com.serena.backend.model; public class TokenUser { private String userId; diff --git a/backend/src/main/java/com/hackathon/backend/model/VoteType.java b/backend/src/main/java/com/serena/backend/model/VoteType.java similarity index 55% rename from backend/src/main/java/com/hackathon/backend/model/VoteType.java rename to backend/src/main/java/com/serena/backend/model/VoteType.java index c3eac23..11a5750 100644 --- a/backend/src/main/java/com/hackathon/backend/model/VoteType.java +++ b/backend/src/main/java/com/serena/backend/model/VoteType.java @@ -1,4 +1,4 @@ -package com.hackathon.backend.model; +package com.serena.backend.model; public enum VoteType { UPVOTE, diff --git a/backend/src/main/java/com/hackathon/backend/service/JwtService.java b/backend/src/main/java/com/serena/backend/service/JwtService.java similarity index 69% rename from backend/src/main/java/com/hackathon/backend/service/JwtService.java rename to backend/src/main/java/com/serena/backend/service/JwtService.java index f442860..fc22dc1 100644 --- a/backend/src/main/java/com/hackathon/backend/service/JwtService.java +++ b/backend/src/main/java/com/serena/backend/service/JwtService.java @@ -1,4 +1,4 @@ -package com.hackathon.backend.service; +package com.serena.backend.service; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; @@ -32,6 +32,24 @@ public class JwtService { return createToken(claims, userId); } + // New method to generate token with role (for migrating from + // SimpleTokenService) + public String generateTokenWithRole(String username, String role) { + String userId = java.util.UUID.randomUUID().toString(); + Map claims = new HashMap<>(); + claims.put("username", username); + claims.put("role", role); // "owner" or "client" + return createToken(claims, userId); + } + + public String extractUsername(String token) { + return extractClaim(token, claims -> claims.get("username", String.class)); + } + + public String extractRole(String token) { + return extractClaim(token, claims -> claims.get("role", String.class)); + } + private String createToken(Map extraClaims, String userId) { return Jwts.builder() .claims(extraClaims) @@ -47,6 +65,16 @@ public class JwtService { return (extractedUserId.equals(userId)) && !isTokenExpired(token); } + // Overloaded method for simpler token validation (migrating from + // SimpleTokenService) + public boolean isTokenValid(String token) { + try { + return !isTokenExpired(token); + } catch (Exception e) { + return false; + } + } + private boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); } diff --git a/backend/src/main/java/com/hackathon/backend/service/RadioStationService.java b/backend/src/main/java/com/serena/backend/service/RadioStationService.java similarity index 96% rename from backend/src/main/java/com/hackathon/backend/service/RadioStationService.java rename to backend/src/main/java/com/serena/backend/service/RadioStationService.java index 2284802..92c680c 100644 --- a/backend/src/main/java/com/hackathon/backend/service/RadioStationService.java +++ b/backend/src/main/java/com/serena/backend/service/RadioStationService.java @@ -1,9 +1,9 @@ -package com.hackathon.backend.service; +package com.serena.backend.service; -import com.hackathon.backend.model.RadioStation; -import com.hackathon.backend.model.Song; -import com.hackathon.backend.model.Client; -import com.hackathon.backend.model.VoteType; +import com.serena.backend.model.RadioStation; +import com.serena.backend.model.Song; +import com.serena.backend.model.Client; +import com.serena.backend.model.VoteType; import org.springframework.stereotype.Service; import java.util.ArrayList; diff --git a/backend/src/main/java/com/hackathon/backend/util/AuthUtil.java b/backend/src/main/java/com/serena/backend/util/AuthUtil.java similarity index 92% rename from backend/src/main/java/com/hackathon/backend/util/AuthUtil.java rename to backend/src/main/java/com/serena/backend/util/AuthUtil.java index 0d98d07..8eb3237 100644 --- a/backend/src/main/java/com/hackathon/backend/util/AuthUtil.java +++ b/backend/src/main/java/com/serena/backend/util/AuthUtil.java @@ -1,6 +1,6 @@ -package com.hackathon.backend.util; +package com.serena.backend.util; -import com.hackathon.backend.model.TokenUser; +import com.serena.backend.model.TokenUser; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; diff --git a/backend/src/test/java/com/hackathon/backend/BackendApplicationTests.java b/backend/src/test/java/com/serena/backend/BackendApplicationTests.java similarity index 85% rename from backend/src/test/java/com/hackathon/backend/BackendApplicationTests.java rename to backend/src/test/java/com/serena/backend/BackendApplicationTests.java index 0f7bf2a..cac54fe 100644 --- a/backend/src/test/java/com/hackathon/backend/BackendApplicationTests.java +++ b/backend/src/test/java/com/serena/backend/BackendApplicationTests.java @@ -1,4 +1,4 @@ -package com.hackathon.backend; +package com.serena.backend; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; diff --git a/backend/src/test/java/com/hackathon/backend/service/RadioStationServiceTest.java b/backend/src/test/java/com/serena/backend/service/RadioStationServiceTest.java similarity index 97% rename from backend/src/test/java/com/hackathon/backend/service/RadioStationServiceTest.java rename to backend/src/test/java/com/serena/backend/service/RadioStationServiceTest.java index da9fe25..fe32945 100644 --- a/backend/src/test/java/com/hackathon/backend/service/RadioStationServiceTest.java +++ b/backend/src/test/java/com/serena/backend/service/RadioStationServiceTest.java @@ -1,7 +1,7 @@ -package com.hackathon.backend.service; +package com.serena.backend.service; -import com.hackathon.backend.model.RadioStation; -import com.hackathon.backend.model.Client; +import com.serena.backend.model.RadioStation; +import com.serena.backend.model.Client; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.DisplayName;