Skip to content

Commit

Permalink
Merge pull request #111 from AntonioMrtz/feat/Unify-Song-Services
Browse files Browse the repository at this point in the history
Feat/unify song services
  • Loading branch information
AntonioMrtz authored Mar 24, 2024
2 parents 23df869 + a6bb938 commit 8daeada
Show file tree
Hide file tree
Showing 58 changed files with 2,696 additions and 272 deletions.
14 changes: 13 additions & 1 deletion .github/workflows/backend-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,27 @@ jobs:
working-directory: Backend
run: pip install -r requirements.txt && pip install -r requirements-test.txt

- name: Run tests
- name: Run tests Streaming lambda Architecture
working-directory: Backend/
run: python -m pytest .
env:
MONGO_URI : ${{ secrets.MONGO_URI }}
SECRET_KEY_SIGN : ${{ secrets.SECRET_KEY_SIGN }}
DISTRIBUTION_ID : ${{ secrets.DISTRIBUTION_ID }}
LAMBDA_URL : ${{ secrets.LAMBDA_URL }}
ARCH : "STREAMING_LAMBDA"
ENV_VALUE : "PROD"

- name: Run tests Database Blob Architecture
working-directory: Backend/
run: python -m pytest .
env:
MONGO_URI : ${{ secrets.MONGO_URI }}
SECRET_KEY_SIGN : ${{ secrets.SECRET_KEY_SIGN }}
DISTRIBUTION_ID : ${{ secrets.DISTRIBUTION_ID }}
LAMBDA_URL : ${{ secrets.LAMBDA_URL }}
ARCH : "DB_BLOB"
ENV_VALUE : "PROD"

- name: Fail workflow on test failure
if: ${{ failure() }}
Expand Down
2 changes: 2 additions & 0 deletions Backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
MONGO_URI=mongodb://root:root@localhost:27017/
SECRET_KEY_SIGN=f24e2f3ac557d487b6d879fb2d86f2b2
LAMBDA_URL=https://lambda-url.us-east-1.on.aws/path/
ARCH=STREAMING_LAMBDA
ENV_VALUE=PROD
2 changes: 1 addition & 1 deletion Backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]


#docker build -t spotify_electron_backend_image .
#docker run -d --name spotify_electron_backend -e MONGO_URI=mongo-uri SECRET_KEY_SIGN=secret-key-sign LAMBDA_URL=lambda-url -p 8000:8000 spotify_electron_backend_image
#docker run -d --name spotify_electron_backend -e MONGO_URI=mongo-uri SECRET_KEY_SIGN=secret-key-sign LAMBDA_URL=lambda-url ARCH=STREAMING_LAMBDA ENV_VALUE=PROD -p 8000:8000 spotify_electron_backend_image
1 change: 1 addition & 0 deletions Backend/requirements-test.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pytest==8.0.1
pytest-cov==4.1.0
pytest-mock==3.12.0
pytest-env==1.1.3
Empty file removed Backend/src/__init__.py
Empty file.
62 changes: 62 additions & 0 deletions Backend/src/boostrap/PropertiesManager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import os
from typing import List

from dotenv import load_dotenv
from src.constants.set_up_constants import (
ARCHITECTURE_ENV_NAME,
DEFAULT_ARCHITECTURE,
DISTRIBUTION_ID_ENV_NAME,
ENV_VALUE_ENV_NAME,
LAMBDA_URL_ENV_NAME,
MONGO_URI_ENV_NAME,
PROD,
SECRET_KEY_SIGN_ENV_NAME,
TEST,
)


class _PropertiesManager:
def __init__(self) -> None:
load_dotenv()
self.env_variables = [
MONGO_URI_ENV_NAME,
SECRET_KEY_SIGN_ENV_NAME,
DISTRIBUTION_ID_ENV_NAME,
LAMBDA_URL_ENV_NAME,
ENV_VALUE_ENV_NAME,
]
self._load_env_variables(self.env_variables)
self._load_architecture()

def _load_architecture(self):
# TODO
architecture_type = os.getenv(ARCHITECTURE_ENV_NAME, DEFAULT_ARCHITECTURE)
if not architecture_type:
# TODO convert to log
architecture_type = DEFAULT_ARCHITECTURE
self.__setattr__(ARCHITECTURE_ENV_NAME, DEFAULT_ARCHITECTURE)
print(f"No architecture type selected, using {DEFAULT_ARCHITECTURE}")
self.__setattr__(ARCHITECTURE_ENV_NAME, architecture_type)
# TODO convert to log
print(f"Architecture selected : {architecture_type}")
# TODO
print(f"Running init method for architecture : {architecture_type}")

def _load_env_variables(self, env_names: List[str]):
# TODO
for env_name in env_names:
env_variable_value = os.getenv(env_name)
if not env_variable_value:
# TODO convert to log
print(f"No enviroment variable provided for {env_name}")
continue
self.__setattr__(env_name, env_variable_value)

def is_production_enviroment(self) -> bool:
return self.__getattribute__(ENV_VALUE_ENV_NAME) == PROD

def is_testing_enviroment(self) -> bool:
return self.__getattribute__(ENV_VALUE_ENV_NAME) == TEST


PropertiesManager = _PropertiesManager()
15 changes: 15 additions & 0 deletions Backend/src/constants/set_up_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ARCH_STREAMING_LAMBDA = "STREAMING_LAMBDA"
ARCH_STREAMING_SDK = "STREAMING_SDK"
ARCH_DB_BLOB = "DB_BLOB"

PROD = "PROD"
TEST = "TEST"

ARCHITECTURE_ENV_NAME = "ARCH"
DEFAULT_ARCHITECTURE = ARCH_STREAMING_LAMBDA

SECRET_KEY_SIGN_ENV_NAME = "SECRET_KEY_SIGN"
MONGO_URI_ENV_NAME = "MONGO_URI"
DISTRIBUTION_ID_ENV_NAME = "DISTRIBUTION_ID"
LAMBDA_URL_ENV_NAME = "LAMBDA_URL"
ENV_VALUE_ENV_NAME = "ENV_VALUE"
4 changes: 4 additions & 0 deletions Backend/src/constants/song_service_set_up_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
MODULE_PREFIX_NAME = "src.services.song_services."
SONG_SERVICE_STREAMING_LAMBDA_SERVICE_MODULE_NAME = "song_service_aws_lambda"
SONG_SERVICE_STREAMING_SDK_SERVICE_MODULE_NAME = "song_service_aws_sdk"
SONG_SERVICE_DB_BLOB_SERVICE_MODULE_NAME = "song_service_db_blob"
5 changes: 3 additions & 2 deletions Backend/src/database/Database.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import logging
import os

from pymongo.mongo_client import MongoClient
from pymongo.server_api import ServerApi
from src.boostrap.PropertiesManager import PropertiesManager
from src.constants.set_up_constants import MONGO_URI_ENV_NAME

formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
logging.basicConfig(level=logging.WARNING)
Expand Down Expand Up @@ -42,7 +43,7 @@ class Database(metaclass=DatabaseMeta):
def __init__(self):
if Database.connection is None:
try:
uri = os.getenv("MONGO_URI")
uri = getattr(PropertiesManager, MONGO_URI_ENV_NAME)
Database.connection = MongoClient(uri, server_api=ServerApi("1"))[
"SpotifyElectron"
]
Expand Down
81 changes: 49 additions & 32 deletions Backend/src/main.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,58 @@
from contextlib import asynccontextmanager

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from src.boostrap.PropertiesManager import PropertiesManager
from src.middleware.middleware import CheckJwtAuth
from src.routers import artistas, canciones, generos, login, playlists, search, usuarios


@asynccontextmanager
async def lifespan_handler(app: FastAPI):
"""Handles the the before and after events of the app start
Parameters
----------
app : FastAPI
the app object that is going to be created
"""
# TODO print init
app.include_router(playlists.router)
app.include_router(canciones.router)
app.include_router(generos.router)
app.include_router(usuarios.router)
app.include_router(artistas.router)
app.include_router(login.router)
app.include_router(search.router)
yield
# teardown app
# TODO print teardown


app = FastAPI(
title="SpotifyElectronAPI",
description="API created with FastAPI Python to manage backend for \
Spotify Electron App https://github.com/AntonioMrtz/SpotifyElectron",
version="0.0.1",
)

""" Cors disabled """
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost/",
"http://localhost:1212",
"https://localhost:1212/",
"https://localhost",
"https://localhost:1212",
"https://localhost:1212/",
"http://127.0.0.1:8000/",
"http://127.0.0.1:8000",
"http://127.0.0.1:8000/usuarios/",
],
allow_credentials=True,
allow_methods=["POST", "GET", "PUT", "DELETE", "PATCH"],
max_age=3600,
allow_headers=["*"],
description="API created with FastAPI Python to serve \
as backend for Spotify Electron music streaming Desktop App",
version="1.0.0",
lifespan=lifespan_handler,
)

app.add_middleware(CheckJwtAuth)

app.include_router(playlists.router)
app.include_router(canciones.router)
app.include_router(generos.router)
app.include_router(usuarios.router)
app.include_router(artistas.router)
app.include_router(login.router)
app.include_router(search.router)
if PropertiesManager.is_production_enviroment():
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost/",
"http://localhost:1212",
"https://localhost:1212/",
"https://localhost",
"https://localhost:1212",
"https://localhost:1212/",
"http://127.0.0.1:8000/",
"http://127.0.0.1:8000",
],
allow_credentials=True,
allow_methods=["POST", "GET", "PUT", "DELETE", "PATCH"],
max_age=3600,
allow_headers=["*"],
)
app.add_middleware(CheckJwtAuth)
4 changes: 3 additions & 1 deletion Backend/src/middleware/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ class CheckJwtAuth(BaseHTTPMiddleware):
bypass_methods = ["DELETE"]

def bypass_request(self, request: Request):
"""print(request.method)
"""
TODO clean
print(request.method)
print(request.url.path)
print(request.headers)"""
""" print(f"COOKIES = \n {request.cookies}")
Expand Down
2 changes: 0 additions & 2 deletions Backend/src/model/Playlist.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import json
from dataclasses import dataclass

import src.services.song_service as song_service


@dataclass
class Playlist:
Expand Down
20 changes: 20 additions & 0 deletions Backend/src/model/SongBlob.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import json
from dataclasses import dataclass

from src.model.Genre import Genre


@dataclass
class SongBlob:

name: str
artist: str
photo: str
duration: int # In seconds
genre: Genre
file: bytes
number_of_plays: int

def get_json(self) -> json:
song_json = json.dumps(self.__dict__)
return song_json
8 changes: 2 additions & 6 deletions Backend/src/routers/canciones.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,17 @@

import src.services.dto_service as dto_service
import src.services.security_service as security_service
import src.services.song_service as song_service_streaming
from fastapi import APIRouter, Header, HTTPException, UploadFile
from fastapi.responses import Response
from src.model.Genre import Genre
from src.services.song_services.song_service_provider import get_song_service

router = APIRouter(
prefix="/canciones",
tags=["canciones"],
)

""" if "pytest" in modules:
song_service = song_service_database
else: """
song_service = song_service_streaming
song_service = get_song_service()


@router.get("/{nombre}")
Expand Down
8 changes: 4 additions & 4 deletions Backend/src/services/all_users_service.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import src.services.artist_service as artist_service
import src.services.playlist_service as playlist_service
import src.services.song_service as song_service
import src.services.user_service as user_service
from fastapi import HTTPException
from src.model.TokenData import TokenData
from src.model.UserType import User_Type
from src.services.song_services.song_service_provider import get_song_service
from src.services.utils import checkValidParameterString

MAX_NUMBER_PLAYBACK_HISTORY_SONGS = 5

services_map = {
User_Type.USER: user_service,
User_Type.ARTIST: artist_service,
}


MAX_NUMBER_PLAYBACK_HISTORY_SONGS = 5
song_service = get_song_service()


def isArtistOrUser(user_name: str) -> User_Type or null:
Expand Down
7 changes: 5 additions & 2 deletions Backend/src/services/artist_service.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import json
from datetime import datetime
from sys import modules

import bcrypt
import src.services.song_service as song_service
from fastapi import HTTPException
from src.database.Database import Database
from src.model.Artist import Artist
from src.model.TokenData import TokenData
from src.services.song_services.song_service_provider import get_song_service
from src.services.utils import checkValidParameterString

if "pytest" in modules:
Expand All @@ -15,6 +16,8 @@
else:
artist_collection = Database().connection["artista"]

song_service = get_song_service()


def check_user_exists(user_name: str) -> bool:
"""Checks if the user or artists exists
Expand Down Expand Up @@ -397,7 +400,7 @@ def get_artists(names: list) -> list:
return artists


def search_by_name(name: str) -> list:
def search_by_name(name: str) -> json:
"""Returns a list of Artist that contains "name" in their names
Parameters
Expand Down
4 changes: 3 additions & 1 deletion Backend/src/services/dto_service.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from typing import List

import src.services.playlist_service as playlist_service
import src.services.song_service as song_service
from fastapi import HTTPException
from src.model.DTO.PlaylistDTO import PlaylistDTO
from src.model.DTO.SongDTO import SongDTO
from src.model.Genre import Genre
from src.services.song_services.song_service_provider import get_song_service
from src.services.utils import checkValidParameterString

song_service = get_song_service()


def get_song(name: str) -> SongDTO:
"""Returns a song's metadata without his audio file"
Expand Down
Loading

0 comments on commit 8daeada

Please sign in to comment.