Reorganize repository structure for better organization

- Move backend code to swingmusic/ folder
- Move client applications to root level (swingmusic-android, swingmusic-desktop, swingmusic-webclient)
- Remove intermediate backend/ and clients/ folders
- Update README with new folder structure and setup instructions
- Clean and organized repository layout
This commit is contained in:
Tomas Dvorak
2026-03-17 22:34:34 +01:00
parent 17e859dd2f
commit 4c04287800
206 changed files with 14 additions and 7 deletions
-103
View File
@@ -1,103 +0,0 @@
from flask_openapi3 import Tag
from flask_openapi3 import APIBlueprint
from pydantic import BaseModel, Field
from swingmusic.api.auth import admin_required
from swingmusic.config import UserConfig
from swingmusic.db.userdata import PluginTable
from swingmusic.plugins.lastfm import LastFmPlugin
from swingmusic.utils.auth import get_current_userid
bp_tag = Tag(name="Plugins", description="Manage plugins")
api = APIBlueprint("plugins", __name__, url_prefix="/plugins", abp_tags=[bp_tag])
@api.get("/")
def get_all_plugins():
"""
List all plugins
"""
plugins = PluginTable.get_all()
return {"plugins": plugins}
class PluginBody(BaseModel):
plugin: str = Field(description="The plugin name", example="lyrics")
class PluginActivateBody(PluginBody):
active: bool = Field(
description="New plugin active state", example=False, default=False
)
@api.post("/setactive")
@admin_required()
def activate_deactivate_plugin(body: PluginActivateBody):
"""
Activate/Deactivate plugin
"""
name = body.plugin
PluginTable.activate(name, body.active)
return {"message": "OK"}, 200
class PluginSettingsBody(PluginBody):
settings: dict = Field(
description="The new plugin settings", example={"key": "value"}
)
@api.post("/settings")
@admin_required()
def update_plugin_settings(body: PluginSettingsBody):
"""
Update plugin settings
"""
plugin = body.plugin
settings = body.settings
if not plugin or not settings:
return {"error": "Missing plugin or settings"}, 400
PluginTable.update_settings(plugin, settings)
plugin = PluginTable.get_by_name(plugin)
return {"status": "success", "settings": plugin.settings}
class LastFmSessionBody(BaseModel):
token: str = Field(description="The token to use to create the session")
@api.post("/lastfm/session/create")
def create_lastfm_session(body: LastFmSessionBody):
"""
Create a Last.fm session
"""
if not body.token:
return {"error": "Missing token"}, 400
lastfm = LastFmPlugin(current_userid=get_current_userid())
session_key = lastfm.get_session_key(body.token)
if session_key:
config = UserConfig()
current_user = get_current_userid()
config.lastfmSessionKeys[str(current_user)] = session_key
config.lastfmSessionKeys = config.lastfmSessionKeys
return {"status": "success", "session_key": session_key}
@api.post("/lastfm/session/delete")
def delete_lastfm_session():
"""
Delete the Last.fm session
"""
config = UserConfig()
current_user = get_current_userid()
config.lastfmSessionKeys[str(current_user)] = ""
config.lastfmSessionKeys = config.lastfmSessionKeys
return {"status": "success"}
-64
View File
@@ -1,64 +0,0 @@
from flask_openapi3 import Tag
from flask_openapi3 import APIBlueprint
from pydantic import Field
from swingmusic.api.apischemas import TrackHashSchema
from swingmusic.lib.lyrics import Lyrics as Lyrics_class
from swingmusic.plugins.lyrics import Lyrics
from swingmusic.settings import Defaults
from swingmusic.utils.hashing import create_hash
bp_tag = Tag(name="Lyrics Plugin", description="Musixmatch lyrics plugin")
api = APIBlueprint(
"lyricsplugin", __name__, url_prefix="/plugins/lyrics", abp_tags=[bp_tag]
)
class LyricsSearchBody(TrackHashSchema):
title: str = Field(description="The track title ", example=Defaults.API_TRACKNAME)
artist: str = Field(description="The track artist ", example=Defaults.API_ARTISTNAME)
album: str = Field(description="The track track album ", example=Defaults.API_ALBUMNAME)
filepath: str = Field(
description="Track filepath to save the lyrics file relative to",
example="/home/cwilvx/temp/crazy song.mp3",
)
@api.post("/search")
def search_lyrics(body: LyricsSearchBody):
"""
Search for lyrics by title and artist
"""
title = body.title
artist = body.artist
album = body.album
filepath = body.filepath
trackhash = body.trackhash
finder = Lyrics()
data = finder.search_lyrics_by_title_and_artist(title, artist)
if not data:
return {"trackhash": trackhash, "lyrics": None}
perfect_match = data[0]
for track in data:
i_title = track["title"]
i_album = track["album"]
if create_hash(i_title) == create_hash(title) and create_hash(i_album) == create_hash(album):
perfect_match = track
track_id = perfect_match["track_id"]
lrc = finder.download_lyrics(track_id, filepath)
if lrc is not None:
lyrics = Lyrics_class(lrc)
if lyrics.is_synced:
formatted_lyrics = lyrics.format_synced_lyrics()
else:
formatted_lyrics = lyrics.format_unsynced_lyrics()
return {"trackhash": trackhash, "lyrics": formatted_lyrics, "synced": lyrics.is_synced}, 200
return {"trackhash": trackhash, "lyrics": None, "synced": False}, 200
-109
View File
@@ -1,109 +0,0 @@
from typing import Literal
from flask_openapi3 import Tag
from flask_openapi3 import APIBlueprint
from pydantic import BaseModel, Field
from swingmusic.db.userdata import MixTable
from swingmusic.plugins.mixes import MixesPlugin
from swingmusic.store.homepage import HomepageStore
from swingmusic.store.tracks import TrackStore
bp_tag = Tag(name="Mixes Plugin", description="Mixes plugin hehe")
api = APIBlueprint(
"mixesplugin", __name__, url_prefix="/plugins/mixes", abp_tags=[bp_tag]
)
class GetMixesBody(BaseModel):
mixtype: Literal["artists", "tracks"] = Field(description="The type of mix")
@api.get("/<mixtype>")
def get_artist_mixes(path: GetMixesBody):
srcmixes = MixTable.get_all(with_userid=True)
mixes = []
if path.mixtype == "artists":
mixes = [mix.to_dict(convert_timestamp=True) for mix in srcmixes]
elif path.mixtype == "tracks":
plugin = MixesPlugin()
for mix in srcmixes:
custom_mix = plugin.get_track_mix(mix)
if custom_mix:
mixes.append(custom_mix.to_dict(convert_timestamp=True))
seen_mixids = set()
# filter duplicates by trackshash
final_mixes = []
for mix in mixes:
# INFO: Ignore duplicates for artist mixes
if mix["id"] in seen_mixids and path.mixtype == "tracks":
continue
final_mixes.append(mix)
seen_mixids.add(mix["id"])
return final_mixes
class MixQuery(BaseModel):
mixid: str = Field(description="The mix id")
sourcehash: str = Field(description="The sourcehash of the mix")
@api.get("/")
def get_mix(query: MixQuery):
mixtype = ""
match query.mixid[0]:
case "a":
mixtype = "artist_mixes"
case "t":
mixtype = "custom_mixes"
case _:
return {"msg": "Invalid mix ID"}, 400
# INFO: Check if the mix is already in the homepage store
mix = HomepageStore.get_mix(mixtype, query.mixid)
if mix and mix["sourcehash"] == query.sourcehash:
return mix, 200
# INF0: Get the mix from the db
mix = MixTable.get_by_sourcehash(query.sourcehash)
if not mix:
return {"msg": "Mix not found"}, 404
if mixtype == "custom_mixes":
mix = MixesPlugin.get_track_mix(mix)
if not mix:
return {"msg": "Mix not found"}, 404
return mix.to_full_dict(), 200
class SaveMixRequest(BaseModel):
mixid: str = Field(description="The id of the mix")
type: str = Field(description="The type of mix")
sourcehash: str = Field(description="The sourcehash of the mix")
@api.post("/save")
def save_mix(body: SaveMixRequest):
mix_type = body.type
mix_sourcehash = body.sourcehash
if mix_type == "artist":
state = MixTable.save_artist_mix(mix_sourcehash)
elif mix_type == "track":
state = MixTable.save_track_mix(mix_sourcehash)
mix = HomepageStore.find_mix(body.mixid)
if mix:
mix.saved = state
return {"msg": "Mixes saved"}, 200