open api changes

This commit is contained in:
2026-08-04 10:35:08 +02:00
parent c23d8bbb90
commit 9d27fdd8f0
19 changed files with 175 additions and 48 deletions
+9
View File
@@ -11,6 +11,15 @@
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.46/lombok-1.18.46.jar" />
</processorPath>
<module name="musix" />
</profile>
<profile name="Annotation profile for mpv-client" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<processorPath useClasspath="false">
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.46/lombok-1.18.46.jar" />
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.46/lombok-1.18.46.jar" />
</processorPath>
<module name="mpv-client" />
</profile>
</annotationProcessing>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/musix/musix.iml" filepath="$PROJECT_DIR$/musix/musix.iml" />
</modules>
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+1 -1
View File
@@ -1,6 +1,6 @@
/* tslint:disable */
/* eslint-disable */
// Generated using typescript-generator version 4.1.1 on 2026-07-24 16:46:55.
// Generated using typescript-generator version 4.1.1 on 2026-07-26 14:02:29.
export interface Session {
sessionId: string;
@@ -35,7 +35,7 @@ export class YoutubeSearchResult implements OnChanges {
this._album = this.musicRecord.album?.name || 'Album Name';
this._duration = this.musicRecord.duration || '0:00';
this._views = this.musicRecord.views || '0 views';
this._thumbnailUrl = this.musicRecord.thumbnails?.[1]?.url || this._thumbnailUrl;
this._thumbnailUrl = this.musicRecord.thumbnails?.[0]?.url || this._thumbnailUrl;
}
}
@@ -3,6 +3,14 @@
<mat-label>Search</mat-label>
<input matInput [(ngModel)]="searchQuery" placeholder="Enter search query">
</mat-form-field>
<mat-form-field class="filter-field">
<mat-label>Filter</mat-label>
<mat-select [(ngModel)]="searchFilter">
<mat-option *ngFor="let f of filters" [value]="f">{{f}}</mat-option>
</mat-select>
</mat-form-field>
<button mat-raised-button color="primary" (click)="performSearch()">Search</button>
</div>
@@ -11,23 +11,47 @@ import { YoutubeSearchResult } from '../youtube-search-result/youtube-search-res
import { CommonModule } from '@angular/common';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatButtonModule } from '@angular/material/button';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-youtube-search-tab',
imports: [YoutubeSearchResult, CommonModule, MatFormFieldModule, MatInputModule, MatButtonModule, FormsModule],
imports: [
YoutubeSearchResult,
CommonModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatButtonModule,
FormsModule,
],
templateUrl: './youtube-search-tab.html',
styleUrl: './youtube-search-tab.css'
styleUrl: './youtube-search-tab.css',
})
export class YoutubeSearchTab {
private readonly youtubeService = inject(YoutubeControllerService);
searchResults: WritableSignal<MusicRecord[]> = signal([]);
searchQuery: string = '';
searchFilter: QueryDto['filter'] = 'SONGS';
filters: QueryDto['filter'][] = [
'SONGS',
'VIDEOS',
'ALBUMS',
'ARTISTS',
'PLAYLISTS',
'COMMUNITY_PLAYLISTS',
'FEATURED_PLALISTS',
'PROFILES',
'PODCASTS',
'EPISODES',
'NONE',
];
performSearch(): void {
const queryDto: QueryDto = {
queryString: this.searchQuery,
filter: this.searchFilter,
};
this.youtubeService.query(queryDto).subscribe({
@@ -40,8 +64,5 @@ export class YoutubeSearchTab {
console.error('Query error:', error);
},
});
}
}
+7 -2
View File
@@ -88,6 +88,11 @@
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.10.0</version>
</dependency>
</dependencies>
<build>
@@ -168,12 +173,12 @@
</plugins>
</build>
<repositories>
<!--repositories>
<repository>
<id>rpi-nexus</id>
<name>RPI Nexus proxy repository</name>
<url>http://192.168.0.187:7781/repository/maven-central/</url>
</repository>
</repositories>
<repositories-->
</project>
+7 -1
View File
@@ -16,7 +16,13 @@ if(args.limit != None):
limit=args.limit;
if(args.filter != None):
filter=args.filter
tries = 1
ytmusic = YTMusic()
search_results = ytmusic.search(args.query, filter=filter, limit = limit)
while(len(search_results)< limit and tries <10):
limit = limit*2
search_results = ytmusic.search(args.query, filter=filter, limit = limit)
tries = tries + 1
print(search_results)
@@ -1,6 +1,8 @@
package zv.musix.controller;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Description;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
@@ -18,6 +20,12 @@ public class ClientController {
@Autowired
private ClientService clientService;
@Operation(
summary = "Client registration",
description = "End point for MPV clients to register themselves to the server. " +
"This is used to send MPV commands (like 'play') to each MPV client" +
"The request must contain the host url where the server can reach the MPV client"
)
@PostMapping("/register")
public ResponseEntity<Client> registerClient(@RequestBody ClientRegistrationRequestDto clientRegistrationRequestDto){
return ResponseEntity.ok(clientService.register(clientRegistrationRequestDto));
@@ -1,5 +1,6 @@
package zv.musix.controller;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -9,6 +10,10 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping(value = "/health", produces = MediaType.APPLICATION_JSON_VALUE)
public class HealthController {
@Operation(
summary = "Ping Pong",
description = "End point used to check if the server is up and running (mainly for docker compose health check)"
)
@GetMapping("/ping")
public String ping(){
return "pong";
@@ -1,5 +1,6 @@
package zv.musix.controller;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -17,6 +18,10 @@ public class PlayerController {
@Autowired
private PlayerService playerService;
@Operation(
summary = "Play song with id",
description = "Searches the music from the cache and if it founds it, sends a request to each MPV client to play the requested music"
)
@PostMapping("/play/{id}")
public ResponseEntity<String> play(@PathVariable String id){
playerService.play(id);
@@ -1,5 +1,6 @@
package zv.musix.controller;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -16,11 +17,21 @@ public class SessionController {
private SessionService sessionService;
@Operation(
summary = "Session information",
description = "Returns the current state of the session, includes User data, their queues, " +
"The session queue etc etc"
)
@GetMapping
public ResponseEntity<Session> getSession(){
return ResponseEntity.ok(sessionService.getSession());
}
@Operation(
summary = "Add music to user's queue",
description = "Requires music ID and userName on the Authorization Request Header"
)
@PostMapping("/music/add/{musicId}")
public ResponseEntity<User> addMusic(@RequestHeader("Authorization") String userName, @PathVariable String musicId){
return ResponseEntity.ok(sessionService.addMusicToQueue(userName,musicId));
@@ -1,5 +1,6 @@
package zv.musix.controller;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -17,6 +18,12 @@ public class UserController {
@Autowired
SessionService sessionService;
@Operation(
summary = "Add new User to session",
description = "Creates a new user in the session, and returns the user list from the session"
)
@PostMapping
public ResponseEntity<List<User>> createUser(@RequestBody CreateUserRequest userRequest) {
return ResponseEntity.ok(sessionService.addUser(userRequest));
@@ -1,5 +1,6 @@
package zv.musix.controller;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
@@ -8,7 +9,6 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import zv.musix.dto.yt.QueryDto;
import zv.musix.entity.QueryCache;
import zv.musix.python.json.MusicRecord;
import zv.musix.service.YtService;
@@ -22,6 +22,14 @@ public class YoutubeController {
private YtService ytService;
@Operation(
summary = "Youtube search",
description = "Queries youtube based on the given parameters" +
"queryString = The string you are searching for" +
"filter = the type of result you are looking for, for example songs are things youtube.music would display as a result, videos are default youtube, playlists are playlists etc." +
"limit = the number of results to return "
)
@PostMapping("/query")
public ResponseEntity<List<MusicRecord>> query(@RequestBody QueryDto queryDto ) {
return ResponseEntity.ok(ytService.query(queryDto));
@@ -6,7 +6,6 @@ import zv.musix.enums.YTQueryFilter;
@Data
public class QueryDto {
String queryString;
YTQueryFilter filter;
Integer limit;
}
@@ -23,10 +23,10 @@ public class QueryCache {
@TimeToLive
private Long timeoutInSeconds = 600L;
public QueryCache(QueryDto dto){
this.query = dto.getQueryString();
this.filter = dto.getFilter() == null ? YTQueryFilter.SONGS : dto.getFilter();
this.limit = dto.getLimit() == null ? 20 : dto.getLimit() ;
public QueryCache(String query, YTQueryFilter filter, Integer limit ){
this.query = query;
this.filter = filter;
this.limit = limit == null ? 20 : limit ;
this.id = query.hashCode() + filter.label + limit;
}
@@ -45,19 +45,19 @@ public class PythonRunner {
}
public String query_py(QueryDto request){
public String query_py(String query, YTQueryFilter filter, Integer limit){
ArrayList<String> args = new ArrayList<>();
args.add(PYTHON);
args.add(QUERY_PY);
args.add("--query");
args.add(request.getQueryString());
if(request.getFilter() != null && request.getFilter() != YTQueryFilter.NONE){
args.add(query);
if(filter != null && filter != YTQueryFilter.NONE){
args.add("--filter");
args.add(request.getFilter().label);
args.add(filter.label);
}
if(request.getLimit() != null) {
if(limit != null) {
args.add("--limit");
args.add(request.getLimit().toString());
args.add(limit.toString());
}
return runScript(args);
}
@@ -2,10 +2,12 @@ package zv.musix.service;
import com.google.gson.Gson;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.text.similarity.JaroWinklerDistance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import zv.musix.dto.yt.QueryDto;
import zv.musix.entity.QueryCache;
import zv.musix.enums.YTQueryFilter;
import zv.musix.python.PythonRunner;
import zv.musix.python.json.MusicRecord;
import zv.musix.repository.MusicRepository;
@@ -14,8 +16,11 @@ import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
@Log4j2
@@ -30,34 +35,66 @@ public class YtService {
public List<MusicRecord> query(QueryDto request){
JaroWinklerDistance jaroWinkler = new JaroWinklerDistance();
log.trace("Search query in cache: {}", request);
QueryCache qc = new QueryCache(request);
List<QueryCache> queryCacheList = new ArrayList<>();
List<MusicRecord> result = new ArrayList<>();
queryCacheList.add(performSearch(request.getQueryString(),YTQueryFilter.SONGS, request.getLimit()));
//queryCacheList.add(performSearch(request.getQueryString(),YTQueryFilter.VIDEOS, request.getLimit()));
//queryCacheList.add(performSearch(request.getQueryString(),YTQueryFilter.NONE, request.getLimit()));
for (QueryCache qc : queryCacheList){
result.addAll(processQueryResult(qc));
}
for(MusicRecord mr : result){
System.out.println(jaroWinkler.apply("asd","asd"));
log.trace(mr.getTitle() + (jaroWinkler.apply(mr.getTitle() != null ? mr.getTitle().toLowerCase() : "null", request.getQueryString().toLowerCase())));
}
return result;
/*return result.stream().sorted(Comparator.comparingDouble((MusicRecord m) ->
jaroWinkler.apply(m.getTitle() != null ? m.getTitle().toLowerCase() : "null", request.getQueryString().toLowerCase())
)).collect(Collectors.toList());*/
}
private QueryCache performSearch(String queryString, YTQueryFilter filter, Integer limit){
QueryCache qc = new QueryCache(queryString, filter, limit);
Optional<QueryCache> byQuery = queryCacheRepository.findById(qc.getId());
if(byQuery.isPresent()){
log.trace("Search query was found in cache");
return processQueryResult(byQuery.get());
return byQuery.get();
}
log.trace("Search query was NOT found in cache, running python script");
String response = pythonRunner.query_py(request);
if(response.isBlank() || response.isEmpty()){
throw new RuntimeException();
}
qc.setResponse(response);
else{
String response = pythonRunner.query_py(queryString,filter, limit);
if(response.isBlank()){
throw new RuntimeException("python response is empty");
}
qc.setResponse(response);
log.trace("Saving search query: {}",qc);
queryCacheRepository.save(qc);
return processQueryResult(qc);
log.trace("Saving search query: {}",qc);
return queryCacheRepository.save(qc);
}
}
private List<MusicRecord> processQueryResult(QueryCache queryCache){
Gson gson = new Gson();
Type listType = new TypeToken<List<MusicRecord>>(){}.getType();
try{
Gson gson = new Gson();
Type listType = new TypeToken<List<MusicRecord>>(){}.getType();
// Map the entire JSON hierarchy to your parent class
List<MusicRecord> musicRecordList = gson.fromJson(queryCache.getResponse(), listType);
musicRepository.saveAll(musicRecordList);
return musicRecordList;
}
catch (Exception e) {
e.printStackTrace();
return new ArrayList<>();
}
// Map the entire JSON hierarchy to your parent class
List<MusicRecord> musicRecordList = gson.fromJson(queryCache.getResponse(), listType);
System.out.println(musicRecordList);
musicRepository.saveAll(musicRecordList);
return musicRecordList;
}
}