break down the api blueprint into smaller blueprints

This commit is contained in:
geoffrey45 2022-03-20 16:29:31 +03:00
parent 1908633f9d
commit e889d0ef55
24 changed files with 460 additions and 336 deletions

View File

@ -3,12 +3,10 @@ from flask_cors import CORS
from flask_caching import Cache
config = {
"CACHE_TYPE": "SimpleCache",
"CACHE_DEFAULT_TIMEOUT": 300
}
config = {"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 300}
cache = Cache(config=config)
cache = Cache(config = config)
def create_app():
app = Flask(__name__)
@ -18,7 +16,13 @@ def create_app():
cache.init_app(app)
with app.app_context():
from . import api
app.register_blueprint(api.bp, url_prefix='/')
from app.api import artist, track, search, folder, album
app.register_blueprint(album.album_bp, url_prefix="/")
app.register_blueprint(artist.artist_bp, url_prefix="/")
app.register_blueprint(track.track_bp, url_prefix="/")
app.register_blueprint(search.search_bp, url_prefix="/")
app.register_blueprint(folder.folder_bp, url_prefix="/")
return app

View File

@ -1,7 +1,8 @@
import urllib
from typing import List
from app import models, functions, helpers
from app import trackslib, api
from app import trackslib
from app import api
@helpers.background

View File

@ -1,307 +0,0 @@
import os
import urllib
from typing import List
from flask import Blueprint, request, send_file
from app import functions, instances, helpers, cache, models, prep
from app import albumslib, searchlib
from app import trackslib
bp = Blueprint("api", __name__, url_prefix="")
functions.start_watchdog()
DB_TRACKS = instances.songs_instance.get_all_songs()
ALBUMS: List[models.Album] = []
TRACKS: List[models.Track] = []
home_dir = helpers.home_dir
@helpers.background
def initialize() -> None:
"""
Runs all the necessary setup functions.
"""
albumslib.create_everything()
prep.create_config_dir()
functions.reindex_tracks()
initialize()
@bp.route("/")
def say_hi():
"""Returns some text for the default route"""
return "^ _ ^"
SEARCH_RESULTS = {
"tracks": [],
"albums": [],
"artists": [],
}
@bp.route("/search")
def search():
"""
Returns a list of songs, albums and artists that match the search query.
"""
query = request.args.get("q") or "Mexican girl"
albums = searchlib.get_search_albums(query)
artists_dicts = []
artist_tracks = searchlib.get_artists(query)
for song in artist_tracks:
for artist in song.artists:
if query.lower() in artist.lower():
artist_obj = {
"name": artist,
"image": helpers.check_artist_image(artist),
}
if artist_obj not in artists_dicts:
artists_dicts.append(artist_obj)
_tracks = searchlib.get_tracks(query)
tracks = [*_tracks, *artist_tracks]
SEARCH_RESULTS.clear()
SEARCH_RESULTS["tracks"] = tracks
SEARCH_RESULTS["albums"] = albums
SEARCH_RESULTS["artists"] = artists_dicts
return {
"data": [
{"tracks": tracks[:5], "more": len(tracks) > 5},
{"albums": albums[:6], "more": len(albums) > 6},
{"artists": artists_dicts[:6], "more": len(artists_dicts) > 6},
]
}
@bp.route("/search/loadmore")
def search_load_more():
"""
Returns more songs, albums or artists from a search query.
"""
type = request.args.get("type")
start = int(request.args.get("start"))
if type == "tracks":
return {
"tracks": SEARCH_RESULTS["tracks"][start : start + 5],
"more": len(SEARCH_RESULTS["tracks"]) > start + 5,
}
elif type == "albums":
return {
"albums": SEARCH_RESULTS["albums"][start : start + 6],
"more": len(SEARCH_RESULTS["albums"]) > start + 6,
}
elif type == "artists":
return {
"artists": SEARCH_RESULTS["artists"][start : start + 6],
"more": len(SEARCH_RESULTS["artists"]) > start + 6,
}
@bp.route("/populate")
def find_tracks():
"""call the populate function"""
functions.populate()
return "🎸"
@bp.route("/album/artists", methods=["POST"])
def get_albumartists():
"""Returns a list of artists featured in a given album."""
data = request.get_json()
album = data["album"]
artist = data["artist"]
tracks = []
for track in TRACKS:
if track.album == album and track.albumartist == artist:
tracks.append(track)
artists = []
for track in tracks:
for artist in track.artists:
if artist not in artists:
artists.append(artist)
final_artists = []
for artist in artists:
artist_obj = {
"name": artist,
"image": helpers.check_artist_image(artist),
}
final_artists.append(artist_obj)
return {"artists": final_artists}
@bp.route("/populate/images")
def populate_images():
"""
Populates the artist images.
"""
functions.populate_images()
return "Done"
@bp.route("/artist/<artist>")
@cache.cached()
def get_artist_data(artist: str):
"""Returns the artist's data, tracks and albums"""
artist = urllib.parse.unquote(artist)
artist_obj = instances.artist_instance.get_artists_by_name(artist)
def get_artist_tracks():
songs = instances.songs_instance.find_songs_by_artist(artist)
return songs
artist_songs = get_artist_tracks()
songs = helpers.remove_duplicates(artist_songs)
def get_artist_albums():
artist_albums = []
albums_with_count = []
albums = instances.songs_instance.find_songs_by_albumartist(artist)
for song in albums:
if song["album"] not in artist_albums:
artist_albums.append(song["album"])
for album in artist_albums:
count = 0
length = 0
for song in artist_songs:
if song["album"] == album:
count = count + 1
length = length + song["length"]
album_ = {"title": album, "count": count, "length": length}
albums_with_count.append(album_)
return albums_with_count
return {"artist": artist_obj, "songs": songs, "albums": get_artist_albums()}
@bp.route("/f/<folder>")
# @cache.cached()
def get_folder_tree(folder: str):
"""
Returns a list of all the folders and tracks in the given folder.
"""
req_dir = folder.replace("|", "/")
if folder == "home":
req_dir = home_dir
dir_content = os.scandir(os.path.join(home_dir, req_dir))
folders = []
files = []
for entry in dir_content:
if entry.is_dir() and not entry.name.startswith("."):
files_in_dir = helpers.run_fast_scandir(entry.path, [".flac", ".mp3"])[1]
if len(files_in_dir) != 0:
_dir = {
"name": entry.name,
"count": len(files_in_dir),
"path": entry.path.replace(home_dir, ""),
}
folders.append(_dir)
if entry.is_file():
if entry.name.endswith(".flac") or entry.name.endswith(".mp3"):
files.append(entry)
files.sort(key=lambda x: os.path.getmtime(x.path))
songs = []
for entry in files:
for track in TRACKS:
if track.filepath == entry.path:
songs.append(track)
return {
"files": helpers.remove_duplicates(songs),
"folders": sorted(folders, key=lambda i: i["name"]),
}
@bp.route("/albums")
def get_albums():
"""returns all the albums"""
albums = []
for song in DB_TRACKS:
al_obj = {"name": song["album"], "artist": song["artists"]}
if al_obj not in albums:
albums.append(al_obj)
return {"albums": albums}
@bp.route("/album/tracks", methods=["POST"])
def get_album_tracks():
"""Returns all the tracks in the given album."""
data = request.get_json()
album = data["album"]
artist = data["artist"]
songs = trackslib.get_album_tracks(album, artist)
album = albumslib.find_album(album, artist)
return {"songs": songs, "info": album}
@bp.route("/album/<title>/<artist>/bio")
@cache.cached()
def get_album_bio(title, artist):
"""Returns the album bio for the given album."""
bio = functions.get_album_bio(title, artist)
return {"bio": bio}, 200
@bp.route("/file/<trackid>")
def send_track_file(trackid):
"""
Returns an audio file that matches the passed id to the client.
"""
try:
filepath = instances.songs_instance.get_song_by_id(trackid)["filepath"]
return send_file(filepath, mimetype="audio/mp3")
except FileNotFoundError:
return "File not found", 404
@bp.route("/sample")
def get_sample_track():
"""
Returns a sample track object.
"""
return instances.songs_instance.get_song_by_album("Legends Never Die", "Juice WRLD")

View File

@ -0,0 +1,23 @@
from typing import List
from app import models, instances
from app import functions, helpers, albumslib, prep
DB_TRACKS = instances.songs_instance.get_all_songs()
ALBUMS: List[models.Album] = []
TRACKS: List[models.Track] = []
@helpers.background
def initialize() -> None:
"""
Runs all the necessary setup functions.
"""
functions.start_watchdog()
albumslib.create_everything()
prep.create_config_dir()
functions.reindex_tracks()
initialize()

80
server/app/api/album.py Normal file
View File

@ -0,0 +1,80 @@
from flask import Blueprint, request
from app import api
from app import helpers, cache
from app import albumslib, functions, trackslib
album_bp = Blueprint("album", __name__, url_prefix="")
@album_bp.route("/")
def say_hi():
"""Returns some text for the default route"""
return "^ _ ^"
@album_bp.route("/albums")
def get_albums():
"""returns all the albums"""
albums = []
for song in api.DB_TRACKS:
al_obj = {"name": song["album"], "artist": song["artists"]}
if al_obj not in albums:
albums.append(al_obj)
return {"albums": albums}
@album_bp.route("/album/tracks", methods=["POST"])
def get_album_tracks():
"""Returns all the tracks in the given album."""
data = request.get_json()
album = data["album"]
artist = data["artist"]
songs = trackslib.get_album_tracks(album, artist)
album = albumslib.find_album(album, artist)
return {"songs": songs, "info": album}
@album_bp.route("/album/<title>/<artist>/bio")
@cache.cached()
def get_album_bio(title, artist):
"""Returns the album bio for the given album."""
bio = functions.get_album_bio(title, artist)
return {"bio": bio}, 200
@album_bp.route("/album/artists", methods=["POST"])
def get_albumartists():
"""Returns a list of artists featured in a given album."""
data = request.get_json()
album = data["album"]
artist = data["artist"]
tracks = []
for track in api.TRACKS:
if track.album == album and track.albumartist == artist:
tracks.append(track)
artists = []
for track in tracks:
for artist in track.artists:
if artist not in artists:
artists.append(artist)
final_artists = []
for artist in artists:
artist_obj = {
"name": artist,
"image": helpers.check_artist_image(artist),
}
final_artists.append(artist_obj)
return {"artists": final_artists}

27
server/app/api/all.py Normal file
View File

@ -0,0 +1,27 @@
import os
import urllib
from typing import List
from flask import request, send_file
from app import functions, instances, helpers, cache, models, prep
from app import albumslib, searchlib
from app import trackslib
from app import api
home_dir = helpers.home_dir
# @api.bp.route("/populate")
# def find_tracks():
# """call the populate function"""
# functions.populate()
# return "🎸"
# @api.bp.route("/populate/images")
# def populate_images():
# """
# Populates the artist images.
# """
# functions.populate_images()
# return "Done"

51
server/app/api/artist.py Normal file
View File

@ -0,0 +1,51 @@
from flask import Blueprint
import urllib
from app import instances
from app import helpers
artist_bp = Blueprint("artist", __name__, url_prefix="/")
from app import cache
@artist_bp.route("/artist/<artist>")
@cache.cached()
def get_artist_data(artist: str):
"""Returns the artist's data, tracks and albums"""
artist = urllib.parse.unquote(artist)
artist_obj = instances.artist_instance.get_artists_by_name(artist)
def get_artist_tracks():
songs = instances.songs_instance.find_songs_by_artist(artist)
return songs
artist_songs = get_artist_tracks()
songs = helpers.remove_duplicates(artist_songs)
def get_artist_albums():
artist_albums = []
albums_with_count = []
albums = instances.songs_instance.find_songs_by_albumartist(artist)
for song in albums:
if song["album"] not in artist_albums:
artist_albums.append(song["album"])
for album in artist_albums:
count = 0
length = 0
for song in artist_songs:
if song["album"] == album:
count = count + 1
length = length + song["length"]
album_ = {"title": album, "count": count, "length": length}
albums_with_count.append(album_)
return albums_with_count
return {"artist": artist_obj, "songs": songs, "albums": get_artist_albums()}

55
server/app/api/folder.py Normal file
View File

@ -0,0 +1,55 @@
import os
from flask import Blueprint
from app import api
folder_bp = Blueprint("folder", __name__, url_prefix="/")
from app import helpers
@folder_bp.route("/f/<folder>")
# @cache.cached()
def get_folder_tree(folder: str):
"""
Returns a list of all the folders and tracks in the given folder.
"""
req_dir = folder.replace("|", "/")
if folder == "home":
req_dir = helpers.home_dir
dir_content = os.scandir(os.path.join(helpers.home_dir, req_dir))
folders = []
files = []
for entry in dir_content:
if entry.is_dir() and not entry.name.startswith("."):
files_in_dir = helpers.run_fast_scandir(entry.path, [".flac", ".mp3"])[1]
if len(files_in_dir) != 0:
_dir = {
"name": entry.name,
"count": len(files_in_dir),
"path": entry.path.replace(helpers.home_dir, ""),
}
folders.append(_dir)
if entry.is_file():
if entry.name.endswith(".flac") or entry.name.endswith(".mp3"):
files.append(entry)
files.sort(key=lambda x: os.path.getmtime(x.path))
songs = []
for entry in files:
for track in api.TRACKS:
if track.filepath == entry.path:
songs.append(track)
return {
"files": helpers.remove_duplicates(songs),
"folders": sorted(folders, key=lambda i: i["name"]),
}

View File

@ -0,0 +1,35 @@
from flask import Blueprint, request
from app import instances
playlist_bp = Blueprint("playlist", __name__, url_prefix="/")
@playlist_bp.route("/playlist/new")
def create_playlist():
data = request.get_json()
playlist = {
"name": data["name"],
"description": data["description"],
}
instances.playlist_instance.insert_playlist(playlist)
return 200
@playlist_bp.route("/playlist/<playlist_id>")
def get_playlist(playlist_id):
pass
@playlist_bp.route("/playlist/tracks/get", methods=["POST"])
def add_tracks_to_playlist():
pass
@playlist_bp.route("/playlist/tracks/remove", methods=["POST"])
def remove_single_track():
pass
@playlist_bp.route("/playlist/<playlist_id>/update/desc", methods=["POST"])
def update_playlist():
pass

80
server/app/api/search.py Normal file
View File

@ -0,0 +1,80 @@
from flask import Blueprint, request
from app import searchlib
from app import helpers
search_bp = Blueprint("search", __name__, url_prefix="/")
SEARCH_RESULTS = {
"tracks": [],
"albums": [],
"artists": [],
}
@search_bp.route("/search")
def search():
"""
Returns a list of songs, albums and artists that match the search query.
"""
query = request.args.get("q") or "Mexican girl"
albums = searchlib.get_search_albums(query)
artists_dicts = []
artist_tracks = searchlib.get_artists(query)
for song in artist_tracks:
for artist in song.artists:
if query.lower() in artist.lower():
artist_obj = {
"name": artist,
"image": helpers.check_artist_image(artist),
}
if artist_obj not in artists_dicts:
artists_dicts.append(artist_obj)
_tracks = searchlib.get_tracks(query)
tracks = [*_tracks, *artist_tracks]
SEARCH_RESULTS.clear()
SEARCH_RESULTS["tracks"] = tracks
SEARCH_RESULTS["albums"] = albums
SEARCH_RESULTS["artists"] = artists_dicts
return {
"data": [
{"tracks": tracks[:5], "more": len(tracks) > 5},
{"albums": albums[:6], "more": len(albums) > 6},
{"artists": artists_dicts[:6], "more": len(artists_dicts) > 6},
]
}
@search_bp.route("/search/loadmore")
def search_load_more():
"""
Returns more songs, albums or artists from a search query.
"""
type = request.args.get("type")
start = int(request.args.get("start"))
if type == "tracks":
return {
"tracks": SEARCH_RESULTS["tracks"][start : start + 5],
"more": len(SEARCH_RESULTS["tracks"]) > start + 5,
}
elif type == "albums":
return {
"albums": SEARCH_RESULTS["albums"][start : start + 6],
"more": len(SEARCH_RESULTS["albums"]) > start + 6,
}
elif type == "artists":
return {
"artists": SEARCH_RESULTS["artists"][start : start + 6],
"more": len(SEARCH_RESULTS["artists"]) > start + 6,
}

26
server/app/api/track.py Normal file
View File

@ -0,0 +1,26 @@
from flask import Blueprint, send_file
from app import instances
track_bp = Blueprint("track", __name__, url_prefix="/")
@track_bp.route("/file/<trackid>")
def send_track_file(trackid):
"""
Returns an audio file that matches the passed id to the client.
"""
try:
filepath = instances.songs_instance.get_song_by_id(trackid)["filepath"]
return send_file(filepath, mimetype="audio/mp3")
except FileNotFoundError:
return "File not found", 404
@track_bp.route("/sample")
def get_sample_track():
"""
Returns a sample track object.
"""
return instances.songs_instance.get_song_by_album("Legends Never Die", "Juice WRLD")

View File

@ -20,8 +20,9 @@ from PIL import Image
from app import helpers
from app import instances
from app import api, settings, watchdoge, models, trackslib
from app import settings, watchdoge, models, trackslib
from app import albumslib
from app import api
def reindex_tracks():
@ -371,6 +372,8 @@ def get_all_albums() -> List[models.Album]:
albums: List[models.Album] = []
for track in api.DB_TRACKS:
albums.append(albumslib.create_album(track))
xx = albumslib.create_album(track)
if xx not in albums:
albums.append(xx)
return albums

View File

@ -114,6 +114,6 @@ def check_artist_image(image: str) -> str:
img_name = image.replace("/", "::") + ".webp"
if not os.path.exists(os.path.join(app_dir, "images", "artists", img_name)):
return "http://0.0.0.0:8900/images/artists/0.webp"
return "http://10.5.8.182:8900/images/artists/0.webp"
else:
return ("http://0.0.0.0:8900/images/artists/" + img_name,)
return ("http://10.5.8.182:8900/images/artists/" + img_name,)

View File

@ -1,6 +1,7 @@
from app.models import AllSongs, Artists, TrackColors, Albums
from app.models import AllSongs, Artists, TrackColors, Albums, Playlists
songs_instance = AllSongs()
artist_instance = Artists()
track_color_instance = TrackColors()
album_instance = Albums()
playlist_instance = Playlists()

View File

@ -314,6 +314,47 @@ class Albums(Mongo):
return convert_one(album)
class Playlists(Mongo):
"""
The class for all playlist-related database operations.
"""
def __init__(self):
super(Playlists, self).__init__("PLAYLISTS")
self.collection = self.db["PLAYLISTS"]
def insert_playlist(self, playlist: dict) -> None:
"""
Inserts a new playlist object into the database.
"""
return self.collection.update_one(
{"name": playlist["name"]},
{"$set": playlist},
upsert=True,
).upserted_id
def get_all_playlists(self) -> list:
"""
Returns all the playlists in the database.
"""
playlists = self.collection.find()
return convert_many(playlists)
def get_playlist_by_id(self, id: str) -> dict:
"""
Returns a single playlist matching the id in the query params.
"""
playlist = self.collection.find_one({"_id": ObjectId(id)})
return convert_one(playlist)
def get_playlist_by_name(self, name: str) -> dict:
"""
Returns a single playlist matching the name in the query params.
"""
playlist = self.collection.find_one({"name": name})
return convert_one(playlist)
@dataclass
class Album:
"""
@ -334,5 +375,7 @@ class Album:
self.count = tags["count"]
self.duration = tags["duration"]
self.date = tags["date"]
self.artistimage = "http://0.0.0.0:8900/images/artists/" + tags["artistimage"]
self.image = "http://0.0.0.0:8900/images/thumbnails/" + tags["image"]
self.artistimage = "http://10.5.8.182:8900/images/artists/" + tags["artistimage"]
self.image = "http://10.5.8.182:8900/images/thumbnails/" + tags["image"]

View File

@ -1,5 +1,6 @@
from typing import List
from app import models, api, albumslib, helpers
from app import models, albumslib, helpers
from app import api
def get_tracks(query: str) -> List[models.Track]:

View File

@ -2,8 +2,9 @@ import os
from trace import Trace
from typing import List
from app import models, instances
from app import albumslib, api
from app import albumslib
from app.helpers import remove_duplicates
from app import api
def create_all_tracks() -> List[models.Track]:
"""
@ -29,7 +30,7 @@ def create_all_tracks() -> List[models.Track]:
def get_album_tracks(albumname, artist):
"""Returns all tracks matching an album"""
"""Returns api tracks matching an album"""
_tracks: List[models.Track] = []
for track in api.TRACKS:

View File

@ -6,8 +6,9 @@ from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
from app import instances, functions
from app import api, models
from app import models
from app import albumslib
from app import api
class OnMyWatch:
@ -49,7 +50,6 @@ def add_track(filepath: str) -> None:
api.TRACKS.append(models.Track(track))
def remove_track(filepath: str) -> None:
"""
Removes a track from the music dict.

View File

@ -23,7 +23,7 @@ const tabs = useTabStore();
<style lang="scss">
.tabs {
padding: $small;
height: 4rem;
height: 4.25rem;
.cont {
background-color: $primary;

View File

@ -1,4 +1,4 @@
let base_uri = "http://0.0.0.0:9876";
let base_uri = "http://10.5.8.182:9876/";
const getTracksAndDirs = async (path) => {
let url;

View File

@ -1,6 +1,6 @@
import axios from "axios";
const url = "http://0.0.0.0:9876/search/loadmore";
const url = "http://10.5.8.182:9876/search/loadmore";
async function loadMoreTracks(start) {
const response = await axios.get(url, {

View File

@ -9,7 +9,7 @@ const current = ref(state.current);
const next = ref({
title: "The next song",
artists: ["... blah blah blah"],
image: "http://0.0.0.0:8900/images/defaults/4.webp",
image: "http://10.5.8.182:8900/images/defaults/4.webp",
_id: {
$oid: "",
},

View File

@ -11,7 +11,7 @@ const current_time = ref(0);
const playing = ref(state.is_playing);
const url = "http://0.0.0.0:9876/file/";
const url = "http://10.5.8.182:9876//file/";
const playAudio = (trackid) => {
const elem = document.getElementById('progress');

View File

@ -8,7 +8,7 @@ const queue = ref(
Array<i.Track>({
title: "Nothing played yet",
artists: ["... blah blah blah"],
image: "http://0.0.0.0:8900/images/thumbnails/4.webp",
image: "http://10.5.8.182:8900/images/thumbnails/4.webp",
trackid: "",
})
);
@ -19,14 +19,14 @@ const folder_list = ref([]);
const current = ref(<i.Track>{
title: "Nothing played yet",
artists: ["... blah blah blah"],
image: "http://0.0.0.0:8900/images/thumbnails/4.webp",
image: "http://10.5.8.182:8900/images/thumbnails/4.webp",
trackid: "",
});
const prev = ref(<i.Track>{
title: "Nothing played yet",
artists: ["... blah blah blah"],
image: "http://0.0.0.0:8900/images/thumbnails/4.webp",
image: "http://10.5.8.182:8900/images/thumbnails/4.webp",
trackid: "",
});
@ -40,7 +40,7 @@ const album = reactive({
const loading = ref(false);
const is_playing = ref(false);
const settings = reactive({
uri: "http://0.0.0.0:9876",
uri: "http://10.5.8.182:9876",
});
export default {