diff --git a/app/database/models.py b/app/database/models.py index f06f6d54..6ab61a8f 100644 --- a/app/database/models.py +++ b/app/database/models.py @@ -217,6 +217,36 @@ def __repr__(self): return f"" +class UserSettings(Base): + __tablename__ = "user_settings" + + id = Column(Integer, primary_key=True, index=True) + user_id = Column(Integer, ForeignKey("users.id")) + music_on = Column(Boolean, default=False, nullable=False) + music_vol = Column(Integer, default=None) + sfx_on = Column(Boolean, default=False, nullable=False) + sfx_vol = Column(Integer, default=None) + primary_cursor = Column(String, default="default", nullable=False) + secondary_cursor = Column(String, default="default", nullable=False) + video_game_releases = Column(Boolean, default=False) + + +class AudioTracks(Base): + __tablename__ = "audio_tracks" + + id = Column(Integer, primary_key=True, index=True) + title = Column(String, nullable=False, unique=True) + is_music = Column(Boolean, nullable=False) + + +class UserAudioTracks(Base): + __tablename__ = "user_audio_tracks" + + id = Column(Integer, primary_key=True, index=True) + user_id = Column(Integer, ForeignKey("users.id")) + track_id = Column(Integer, ForeignKey("audio_tracks.id")) + + class OAuthCredentials(Base): __tablename__ = "oauth_credentials" diff --git a/app/dependencies.py b/app/dependencies.py index 69a6ca42..26f7a4e0 100644 --- a/app/dependencies.py +++ b/app/dependencies.py @@ -13,7 +13,7 @@ MEDIA_PATH = os.path.join(APP_PATH, config.MEDIA_DIRECTORY) STATIC_PATH = os.path.join(APP_PATH, "static") TEMPLATES_PATH = os.path.join(APP_PATH, "templates") - +SOUNDS_PATH = os.path.join(STATIC_PATH, "tracks") templates = Jinja2Templates(directory=TEMPLATES_PATH) templates.env.add_extension('jinja2.ext.i18n') diff --git a/app/internal/audio.py b/app/internal/audio.py new file mode 100644 index 00000000..1dbd68e7 --- /dev/null +++ b/app/internal/audio.py @@ -0,0 +1,246 @@ +from sqlalchemy.orm.session import Session +from typing import Dict, List, Optional, Tuple, Union +from enum import Enum +from app.database.models import ( + AudioTracks, + User, + UserAudioTracks, + UserSettings, +) + + +DEFAULT_MUSIC = ["GASTRONOMICA.mp3"] +DEFAULT_MUSIC_VOL = 0.5 +DEFAULT_SFX = "click_1.wav" +DEFAULT_SFX_VOL = 0.5 + + +class SoundKind(Enum): + SONG = 1 + SFX = 0 + + +class Sound: + def __init__(self, sound_kind, name, src): + self.sound_kind = sound_kind + self.name = name + self.src = src + + +def init_audio_tracks(session: Session, sounds: List[Sound]): + """This function fills the AudioTracks table + + Args: + session (Session): the database + sounds (List[Sound]): list of sounds + """ + for sound in sounds: + add_sound(session, sound) + + +def add_sound(session: Session, sound: Sound): + """Adds a new audio track to AudioTracks table. + + Args: + session (Session): the databse. + sound (Sound): song or sfx. + """ + res = session.query(AudioTracks).filter_by(title=sound.name).first() + if not res: + track = AudioTracks(title=sound.name, is_music=sound.sound_kind.value) + session.add(track) + session.commit() + + +def get_tracks( + session: Session, + user_id: int, +) -> Tuple[List[str], Optional[str]]: + """Retrieves audio selections from the database, + for both music and sound effects. + + Args: + session (Session): the database. + user_id (int): current users' id. + + Returns: + Tuple[Optional[List[str]], Optional[str]]: + returns the playlist of music tracks, as well as sound effect choice. + """ + playlist = [] + + chosen_track_ids = session.query(UserAudioTracks.track_id).filter_by( + user_id=user_id, + ) + + tracks = ( + session.query(AudioTracks) + .filter(AudioTracks.id.in_(chosen_track_ids)) + .filter_by(is_music=1) + ) + + sfx = ( + session.query(AudioTracks) + .filter(AudioTracks.id.in_(chosen_track_ids)) + .filter_by(is_music=0) + .first() + ) + + for track in tracks: + playlist.append(track.title + ".mp3") + sfx_choice = sfx.title + ".wav" if sfx else None + + return playlist, sfx_choice + + +def get_audio_settings( + session: Session, + user_id: int, +) -> Tuple[Optional[List[str]], Optional[int], Optional[str], Optional[int]]: + """Retrieves audio settings from the database. + + Args: + session (Session): [description] + user_id (int, optional): [description]. Defaults to 1. + + Returns: + Tuple[str, Optional[List[str]], Optional[int], + str, Optional[str], Optional[int]]: the audio settings. + """ + music_on, music_vol, sfx_on, sfx_vol = None, None, None, None + playlist, sfx_choice = get_tracks(session, user_id) + audio_settings = ( + session.query(UserSettings).filter_by(user_id=user_id).first() + ) + if audio_settings: + music_on = audio_settings.music_on + music_vol = audio_settings.music_vol + sfx_on = audio_settings.sfx_on + sfx_vol = audio_settings.sfx_vol + + return music_on, playlist, music_vol, sfx_on, sfx_choice, sfx_vol + + +def handle_vol( + is_audio_on: bool, + vol: Optional[int], +) -> Optional[int]: + """Helper function that normalizes the volume and returns it, + if audio is on. + + Args: + is_audio_on (bool): True if the user chose to enable, False otherwise. + vol (Optional[int]): a number in the range (0, 1), + indicating the volume. + example: 0.4 means 40% of the tracks' volume. + + Returns: + Optional[int]: returns the normalized volume, or None if audio + is disabled. + """ + if is_audio_on: + vol /= 100 + + return vol + + +# Functions for saving users' choices in the db. + + +def save_audio_settings( + session: Session, + music_choices: Optional[List[str]], + sfx_choice: Optional[str], + user_choices: Dict[str, Union[str, int]], + user: User, +): + """Save audio settings in the db. + + Args: + session (Session): the database + music_choices (Optional[List[str]]): a list of music tracks + if music is enabled, None otherwise. + sfx_choice (Optional[str]): choice for sound effect. + user_choices (Dict[str, Union[str, int]]): + including music_on, music_vol, sfx_on, sfx_vol + user (User): current user + """ + handle_audio_settings(session, user.user_id, user_choices) + handle_user_audio_tracks(session, user.user_id, music_choices, sfx_choice) + + +def handle_audio_settings( + session: Session, + user_id: int, + user_choices: Dict[str, Union[str, int]], +): + """Insert or update a new record into UserSettings table. + The table stores the following information: + music on, music_vol, sfx on, sfx_vol. + + Args: + session (Session): the database + user_id (int): current users' id. + user_choices (Dict[str, Union[str, int]]): + including music_on, music_vol, sfx_on, sfx_vol + """ + audio_settings = ( + session.query(UserSettings).filter_by(user_id=user_id).first() + ) + if not audio_settings: + audio_settings = UserSettings(user_id=user_id, **user_choices) + session.add(audio_settings) + + else: + session.query(UserSettings).filter_by( + user_id=audio_settings.user_id, + ).update(user_choices) + + session.commit() + + +def handle_user_audio_tracks( + session: Session, + user_id: int, + music_choices: Optional[List[str]], + sfx_choice: Optional[str], +): + """[summary] + + Args: + session (Session): the database. + user_id (int): current users' id. + music_choices (Optional[List[str]]): + a list of music tracks if music is enabled, None otherwise. + sfx_choice (Optional[str]): choice for sound effect. + """ + user_audio_tracks = session.query(UserAudioTracks).filter_by( + user_id=user_id, + ) + if user_audio_tracks: + for record in user_audio_tracks: + session.delete(record) + session.commit() + + if music_choices: + for track in music_choices: + create_new_user_audio_record(session, track, user_id) + if sfx_choice: + create_new_user_audio_record(session, sfx_choice, user_id) + + +def create_new_user_audio_record(session: Session, choice, user_id: int): + """Creates a new UserAudioTracks record. + This is the table that connects users and audio_tracks tables. + + Args: + session (Session): the database. + choice ([type]): title of music track or sound effect. + user_id (int): current users' id. + """ + choice = choice.split(".", maxsplit=1)[0] + track = session.query(AudioTracks).filter_by(title=choice).first() + track_id = track.id + record = UserAudioTracks(user_id=user_id, track_id=track_id) + session.add(record) + session.commit() diff --git a/app/main.py b/app/main.py index 203ce0a4..aa95cc21 100644 --- a/app/main.py +++ b/app/main.py @@ -8,7 +8,14 @@ from app import config from app.database import engine, models -from app.dependencies import get_db, logger, MEDIA_PATH, STATIC_PATH, templates +from app.dependencies import ( + get_db, + logger, + MEDIA_PATH, + SOUNDS_PATH, + STATIC_PATH, + templates, +) from app.internal import daily_quotes, json_data_loader from app.internal.languages import set_ui_language from app.internal.security.ouath2 import auth_exception_handler @@ -17,11 +24,11 @@ def create_tables(engine, psql_environment): - if 'sqlite' in str(engine.url) and psql_environment: + if "sqlite" in str(engine.url) and psql_environment: raise models.PSQLEnvironmentError( "You're trying to use PSQL features on SQLite env.\n" "Please set app.config.PSQL_ENVIRONMENT to False " - "and run the app again." + "and run the app again.", ) else: models.Base.metadata.create_all(bind=engine) @@ -32,6 +39,7 @@ def create_tables(engine, psql_environment): app = FastAPI(title="Pylander", docs_url=None) app.mount("/static", StaticFiles(directory=STATIC_PATH), name="static") app.mount("/media", StaticFiles(directory=MEDIA_PATH), name="media") +app.mount("/static/tracks", StaticFiles(directory=SOUNDS_PATH), name="sounds") app.logger = logger app.add_exception_handler(status.HTTP_401_UNAUTHORIZED, auth_exception_handler) @@ -40,10 +48,33 @@ def create_tables(engine, psql_environment): set_ui_language() from app.routers import ( # noqa: E402 - about_us, agenda, calendar, categories, celebrity, credits, - currency, dayview, email, event, export, four_o_four, friendview, - google_connect, invitation, joke, login, logout, profile, - register, search, telegram, user, weekview, weight, whatsapp, + about_us, + agenda, + audio, + calendar, + categories, + celebrity, + credits, + currency, + dayview, + email, + event, + export, + four_o_four, + friendview, + google_connect, + invitation, + joke, + login, + logout, + profile, + register, + search, + telegram, + user, + weekview, + weight, + whatsapp, ) json_data_loader.load_to_database(next(get_db())) @@ -68,6 +99,7 @@ async def swagger_ui_redirect(): routers_to_include = [ about_us.router, agenda.router, + audio.router, calendar.router, categories.router, celebrity.router, @@ -105,10 +137,13 @@ async def swagger_ui_redirect(): @logger.catch() async def home(request: Request, db: Session = Depends(get_db)): quote = daily_quotes.get_quote_of_day(db) - return templates.TemplateResponse("index.html", { - "request": request, - "quote": quote, - }) + return templates.TemplateResponse( + "index.html", + { + "request": request, + "quote": quote, + }, + ) custom_openapi(app) diff --git a/app/media/new_user.png b/app/media/new_user.png new file mode 100644 index 00000000..bd856aaa Binary files /dev/null and b/app/media/new_user.png differ diff --git a/app/routers/audio.py b/app/routers/audio.py new file mode 100644 index 00000000..827f67ca --- /dev/null +++ b/app/routers/audio.py @@ -0,0 +1,155 @@ +import json +from pathlib import Path +from typing import List, Optional + +from app.database.models import User +from app.internal.audio import ( + get_audio_settings, + handle_vol, + SoundKind, + Sound, + init_audio_tracks, + save_audio_settings, + DEFAULT_MUSIC, + DEFAULT_MUSIC_VOL, + DEFAULT_SFX, + DEFAULT_SFX_VOL, +) +from app.dependencies import SOUNDS_PATH, get_db, templates +from app.internal.security.dependancies import current_user +from fastapi import APIRouter, Depends, Form, Request +from sqlalchemy.orm.session import Session +from starlette.responses import RedirectResponse +from starlette.status import HTTP_302_FOUND + + +router = APIRouter( + prefix="/audio", + tags=["audio"], + responses={404: {"description": "Not found"}}, +) + + +@router.get("/settings") +def audio_settings( + request: Request, + session: Session = Depends(get_db), + user: User = Depends(current_user), +) -> templates.TemplateResponse: + """A route to the audio settings. + + Args: + request (Request): the http request + session (Session): the database. + + Returns: + templates.TemplateResponse: renders the audio.html page + with the relevant information. + """ + mp3_files = Path(SOUNDS_PATH).glob("**/*.mp3") + wav_files = Path(SOUNDS_PATH).glob("**/*.wav") + songs = [Sound(SoundKind.SONG, path.stem, path) for path in mp3_files] + sfxs = [Sound(SoundKind.SFX, path.stem, path) for path in wav_files] + sounds = songs + sfxs + init_audio_tracks(session, sounds) + + return templates.TemplateResponse( + "audio_settings.html", + { + "request": request, + "songs": songs, + "sound_effects": sfxs, + }, + ) + + +@router.post("/settings") +async def get_choices( + session: Session = Depends(get_db), + music_on: bool = Form(...), + music_choices: Optional[List[str]] = Form(None), + music_vol: Optional[int] = Form(None), + sfx_on: bool = Form(...), + sfx_choice: Optional[str] = Form(None), + sfx_vol: Optional[int] = Form(None), + user: User = Depends(current_user), +) -> RedirectResponse: + """This function saves users' choices in the db. + + Args: + request (Request): the http request + session (Session): the database. + music_on_off (str, optional): On if the user chose to enable music, + false otherwise. + music_choices (Optional[List[str]], optional): a list of music tracks + if music is enabled, None otherwise. + music_vol (Optional[int], optional): a number in the range (0, 1) + indicating the desired music volume, or None if disabled. + sfx_on_off (str, optional): On if the user chose to enable + sound effects, false otherwise. + sfx_choice (Optional[str], optional): chosen sound effect for + mouse click if sound effects are enabled, None otherwise. + sfx_vol (Optional[int], optional): a number in the range (0, 1) + indicating the desired sfx volume, or None if disabled. + user (User): current user. + + Returns: + RedirectResponse: redirect the user to home.html. + """ + user_choices = { + "music_on": music_on, + "music_vol": music_vol, + "sfx_on": sfx_on, + "sfx_vol": sfx_vol, + } + save_audio_settings(session, music_choices, sfx_choice, user_choices, user) + + return RedirectResponse("/", status_code=HTTP_302_FOUND) + + +@router.get("/start") +async def start_audio( + session: Session = Depends(get_db), + user: User = Depends(current_user), +) -> RedirectResponse: + """Starts audio according to audio settings. + + Args: + session (Session): the database. + + Returns: + RedirectResponse: redirect the user to home.html. + """ + ( + music_on, + playlist, + music_vol, + sfx_on, + sfx_choice, + sfx_vol, + ) = get_audio_settings(session, user.user_id) + if music_on is not None: + music_vol = handle_vol(music_on, music_vol) + sfx_vol = handle_vol(sfx_on, sfx_vol) + + if not playlist: + playlist = DEFAULT_MUSIC + music_vol = DEFAULT_MUSIC_VOL + + if not sfx_choice: + chosen_sfx = DEFAULT_SFX + chosen_sfx_vol = DEFAULT_SFX_VOL + else: + chosen_sfx = sfx_choice + chosen_sfx_vol = sfx_vol + + return json.dumps( + { + "music_on": music_on, + "playlist": playlist, + "music_vol": music_vol, + "sfx_on": sfx_on, + "sfx_choice": chosen_sfx, + "sfx_vol": chosen_sfx_vol, + }, + ) diff --git a/app/static/audio_settings.js b/app/static/audio_settings.js new file mode 100644 index 00000000..1e8f49cb --- /dev/null +++ b/app/static/audio_settings.js @@ -0,0 +1,174 @@ +// Event listeners +const set_checked_ids = [ + "music-on", + "music-off", + "sound-effects-on", + "sound-effects-off", +]; +const other_ids_and_their_funcs = [ + ["activate", set_default], + ["on", start_audio], + ["off", stop_audio], +]; + +window.addEventListener("load", function () { + set_checked_ids.forEach((val) => { + add_set_checked_listener(val); + }); + other_ids_and_their_funcs.forEach((val) => { + perform_func_on_click(val[0], val[1]); + }); +}); + +/** + * @summary This function gets an element_id and adds eventListener to it. + * upon activation the set_checked function runs, + * with the element_id as argument. + * @param {string} element_id - the id attribute of the html element. One of: Music On/off, Sound Effects On/Off. + */ +function add_set_checked_listener(element_id) { + const elem = document.getElementById(element_id); + if (elem) { + elem.addEventListener("click", function () { + set_checked(element_id); + }); + } +} + +/** + * @summary This function gets an element_id and a function and + * adds eventListener to the element with element_id. + * upon activation the function supplied runs with no arguments. + * @param {function} func - the function to run. + * @param {string} element_id - the id attribute of the html element. + * One of: Music On/off, Sound Effects On/Off. + */ +function perform_func_on_click(element_id, func) { + const elem = document.getElementById(element_id); + if (elem) { + elem.addEventListener("click", func); + } +} + +/** + * @summary This function gets an element_id and set its checked attribute. + * According to the element_id, we disable or enable track selection and volume change for that audio type. + * @param {string} element_id - the id attribute of the html element. One of: Music On/off, Sound Effects On/Off. + */ +function set_checked(element_id) { + const is_music = element_id.includes("music"); + const is_off = element_id.includes("off"); + const to_toggle = is_music ? "music" : "sfx"; + set_disabled_or_enabled(to_toggle, is_music, is_off); +} + +/** + * @summary This function sets audio options off by default. + */ +function set_default() { + set_default_for_audio_type("music-on", "music-off"); + set_default_for_audio_type("sound-effects-on", "sound-effects-off"); +} + +/** + * @summary This function gets class or id name, boolean value to tell if class or id, and bolean value to set, + * And sets the disabled attribute of the corresponding element accordingly. + * @param {string} name - name of the elements' class or id. + * @param {Boolean} is_class - class if true, id otherwise. + * @param {Boolean} to_set - we set the disabled attribute if true, false oterwise. + */ +function set_disabled_or_enabled(name, is_class, to_set) { + if (is_class) { + let elements = document.getElementsByClassName(name); + for (let element of elements) { + element.disabled = to_set; + } + } else { + document.getElementById(name).disabled = to_set; + } + document.getElementById("rangeInput-" + name).disabled = to_set; +} + +/** + * @summary This function is an helper function for the set_default function, + * and we use it to privent code duplication by having one function to handle music as well as sound effects. + * @param {string} audio_id_on - the id corresponding to the On option of the element, for Music as well as sfx. + * @param {string} audio_id_off - the id corresponding to the Off option of the element, for Music as well as sfx. + */ +function set_default_for_audio_type(audio_id_on, audio_id_off) { + const is_on = document.getElementById(audio_id_on).checked; + const is_off = document.getElementById(audio_id_off).checked; + if (!is_on && !is_off) { + document.getElementById(audio_id_off).checked = true; + } +} + +function prepare_audio() { + let audio_settings = JSON.parse(this.response); + const music = document.getElementById("my-audio"); + const sfx = document.getElementById("sfx"); + audio_settings = JSON.parse(audio_settings); + const music_on = audio_settings["music_on"]; + + if (music.muted && (music_on || music_on == null)) { + const choices = audio_settings["playlist"]; + music.src = `/static/tracks/${ + choices[Math.floor(Math.random() * choices.length)] + }`; + music.volume = audio_settings["music_vol"]; + music.muted = false; + } + + if (music.paused) { + music.play(); + } + + const sfx_on = audio_settings["sfx_on"]; + if (sfx.muted && (sfx_on || sfx_on == null)) { + const sfx_choice = audio_settings["sfx_choice"]; + sfx.src = "/static/tracks/" + sfx_choice; + sfx.volume = audio_settings["sfx_vol"]; + sfx.muted = false; + } + + if (!sfx.muted) { + document.body.addEventListener("click", play_sfx, true); + } +} + +/** + * @summary This function loads user choices and starts audio. + */ +function start_audio() { + const request = new XMLHttpRequest(); + request.open("GET", "/audio/start", true); + + request.onload = prepare_audio; + request.send(); +} + +/** + * @summary This function plays a sound effect. + */ +function play_sfx() { + const sfx = document.getElementById("sfx"); + sfx.play(); +} + +/** + * @summary This function stops the audio. + */ +function stop_audio() { + const music = document.getElementById("my-audio"); + const sfx = document.getElementById("sfx"); + + if (!music.paused) { + music.pause(); + music.currentTime = 0; + } + + if (!sfx.muted) { + sfx.muted = true; + document.body.removeEventListener("click", play_sfx, false); + } +} diff --git a/app/static/style.css b/app/static/style.css index 8c527fc5..799245ba 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -96,32 +96,66 @@ p { border: none; background-color: whitesmoke; } - + +#on, +#off { + background: rgb(197, 204, 197); + border: 0.2em solid black; + border-radius: 0.5em; + padding: 1em; + margin: 1.5em; + color: rgb(20, 20, 20); + font-size: 1em; + transition-duration: 0.1s; + text-decoration: none; + cursor: pointer; + background-color: whitesmoke; +} + +#activate { + margin-bottom: 1em; +} + +#rangeInput-sfx, +#rangeInput-music { + display: block; +} + +.audio-section { + margin-bottom: 1em; +} + .error-message { - line-height: 0; - color: red; - padding-left: 12.5rem; + line-height: 0; + color: red; + padding-left: 12.5rem; } .subtitle { - font-size: 1.25rem; + font-size: 1.25rem; } .error-message { - line-height: 0; - color: red; - padding-left: 12.5rem; - margin-bottom: 1em; + line-height: 0; + color: red; + padding-left: 12.5rem; + margin-bottom: 1em; } .input-upload-file { - margin-top: 1em; + margin-top: 1em; } .upload-file { - margin: auto 1em auto 0em; + margin: auto 1em auto 0em; } h2.modal-title { font-size: 1.25rem; } + +#sfx { + width: 5rem; + height: 2.5rem; + margin-top: 1rem; +} diff --git a/app/static/tracks/GASTRONOMICA.mp3 b/app/static/tracks/GASTRONOMICA.mp3 new file mode 100644 index 00000000..8e0b7a78 Binary files /dev/null and b/app/static/tracks/GASTRONOMICA.mp3 differ diff --git a/app/static/tracks/PHARMACOKINETICS.mp3 b/app/static/tracks/PHARMACOKINETICS.mp3 new file mode 100644 index 00000000..d6b0c293 Binary files /dev/null and b/app/static/tracks/PHARMACOKINETICS.mp3 differ diff --git a/app/static/tracks/SQUEEK!.mp3 b/app/static/tracks/SQUEEK!.mp3 new file mode 100644 index 00000000..1315a3a8 Binary files /dev/null and b/app/static/tracks/SQUEEK!.mp3 differ diff --git a/app/static/tracks/click_1.wav b/app/static/tracks/click_1.wav new file mode 100644 index 00000000..cd5ac256 Binary files /dev/null and b/app/static/tracks/click_1.wav differ diff --git a/app/static/tracks/click_2.wav b/app/static/tracks/click_2.wav new file mode 100644 index 00000000..4f13724f Binary files /dev/null and b/app/static/tracks/click_2.wav differ diff --git a/app/static/tracks/click_3.wav b/app/static/tracks/click_3.wav new file mode 100644 index 00000000..45aa32c6 Binary files /dev/null and b/app/static/tracks/click_3.wav differ diff --git a/app/static/tracks/click_4.wav b/app/static/tracks/click_4.wav new file mode 100644 index 00000000..bc6f591a Binary files /dev/null and b/app/static/tracks/click_4.wav differ diff --git a/app/static/tracks/credits_and_licenses.txt b/app/static/tracks/credits_and_licenses.txt new file mode 100644 index 00000000..61519c80 --- /dev/null +++ b/app/static/tracks/credits_and_licenses.txt @@ -0,0 +1,8 @@ +-Music- +xnobis(New Colonies): +http://www.newcolonies.com/ +license: https://creativecommons.org/licenses/by-nc-sa/3.0/ + + +-Sound effects- +https://freesound.org/ \ No newline at end of file diff --git a/app/templates/audio_settings.html b/app/templates/audio_settings.html new file mode 100644 index 00000000..ef603d9a --- /dev/null +++ b/app/templates/audio_settings.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} {% block content %} +
+

Audio Settings

+
+
+ + + + + +
+ {% for song in songs %} +
+ + {% endfor %} +
+ + +
+ + + + + +
+ + + + + +
+
+ +{% endblock %} diff --git a/app/templates/base.html b/app/templates/base.html index dc93d52c..fd313c61 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -46,6 +46,9 @@ + @@ -84,9 +87,12 @@ + + + - \ No newline at end of file + diff --git a/app/templates/home.html b/app/templates/home.html index 9405d7e7..f7d8d0d2 100644 --- a/app/templates/home.html +++ b/app/templates/home.html @@ -1,4 +1,4 @@ -{% extends "partials/index/index_base.html" %} +{% extends "base.html" %} {% block content %} @@ -6,6 +6,12 @@
+
+
+
+ Start Audio + Stop Audio +
diff --git a/app/templates/index.html b/app/templates/index.html index 3bc1d8fc..15488dcc 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -15,14 +15,29 @@

- {% if quote %} - {% if not quote.author%} -

"{{ quote.text }}"

- {% else %} -

"{{ quote.text }}"   \ {{quote.author}} -

- {% endif %} - {% endif %} + {% if quote %} + {% if not quote.author%} +

"{{ quote.text }}"

+ {% else %} +

"{{ quote.text }}"   \ {{quote.author}} +

+ {% endif %} + {% endif %} +
+
+ +

diff --git a/app/templates/partials/base.html b/app/templates/partials/base.html index 4338cacd..790a617d 100644 --- a/app/templates/partials/base.html +++ b/app/templates/partials/base.html @@ -28,6 +28,11 @@ integrity="sha512-d9xgZrVZpmmQlfonhQUvTR7lMPtO7NkZMkA0ABN3PHCbKA5nqylQ/yWlFAyY6hYgdF1Qh6nYiuADWwKB4C2WSw==" crossorigin="anonymous"> + + + + + {% endblock head %} {% block title %} Pylendar{% if self.page_name() %} - {% endif %}{% block page_name %}{% endblock %} @@ -37,4 +42,4 @@ {% block body %} {% endblock %} - + \ No newline at end of file diff --git a/app/templates/partials/index/navigation.html b/app/templates/partials/index/navigation.html index c36d2343..d9394182 100644 --- a/app/templates/partials/index/navigation.html +++ b/app/templates/partials/index/navigation.html @@ -23,6 +23,9 @@ place will change later according to the web design --> Agenda + @@ -41,4 +44,4 @@
- \ No newline at end of file + diff --git a/tests/client_fixture.py b/tests/client_fixture.py index c40b9fb8..465cfe8d 100644 --- a/tests/client_fixture.py +++ b/tests/client_fixture.py @@ -7,8 +7,15 @@ from app import main from app.database.models import Base, User from app.routers import ( - agenda, categories, event, friendview, google_connect, - invitation, profile, weight, + agenda, + audio, + categories, + event, + friendview, + google_connect, + invitation, + profile, + weight, ) from app.routers.salary import routes as salary from tests import security_testing_routes @@ -19,12 +26,12 @@ def get_test_placeholder_user() -> User: return User( - username='fake_user', - email='fake@mail.fake', - password='123456fake', - full_name='FakeName', + username="fake_user", + email="fake@mail.fake", + password="123456fake", + full_name="FakeName", language_id=1, - telegram_id='666666', + telegram_id="666666", ) @@ -84,7 +91,8 @@ def profile_test_client() -> Generator[Session, None, None]: Base.metadata.create_all(bind=test_engine) main.app.dependency_overrides[profile.get_db] = get_test_db main.app.dependency_overrides[ - profile.get_placeholder_user] = get_test_placeholder_user + profile.get_placeholder_user + ] = get_test_placeholder_user with TestClient(main.app) as client: yield client @@ -93,6 +101,11 @@ def profile_test_client() -> Generator[Session, None, None]: Base.metadata.drop_all(bind=test_engine) +@pytest.fixture(scope="session") +def audio_test_client() -> Iterator[TestClient]: + yield from create_test_client(audio.get_db) + + @pytest.fixture(scope="session") def security_test_client(): yield from create_test_client(event.get_db) diff --git a/tests/test_audio.py b/tests/test_audio.py new file mode 100644 index 00000000..f7040b9b --- /dev/null +++ b/tests/test_audio.py @@ -0,0 +1,56 @@ +from app.routers.audio import router + +AUDIO_SETTINGS_URL = router.url_path_for("audio_settings") +GET_CHOICES_URL = router.url_path_for("get_choices") +START_AUDIO_URL = router.url_path_for("start_audio") + + +def test_get_settings(audio_test_client): + response = audio_test_client.get(url=AUDIO_SETTINGS_URL) + assert response.ok + assert b"Audio Settings" in response.content + + +def test_start_audio_default(audio_test_client): + response = audio_test_client.get(START_AUDIO_URL) + assert response.ok + + +def test_choices_Off(audio_test_client): + data = {"music_on": False, "sfx_on": False} + response = audio_test_client.post(url=GET_CHOICES_URL, data=data) + assert response.ok + + +def test_choices_On(audio_test_client): + data = { + "music_on": True, + "music_choices": ["GASTRONOMICA.mp3"], + "music_vol": 50, + "sfx_on": True, + "sfx_choice": "click_1.wav", + "sfx_vol": 50, + } + response = audio_test_client.post(url=GET_CHOICES_URL, data=data) + assert response.ok + + +def test_start_audio(audio_test_client): + data = { + "music_on": True, + "music_choices": ["GASTRONOMICA.mp3"], + "music_vol": 50, + "sfx_on": True, + "sfx_choice": "click_1.wav", + "sfx_vol": 50, + } + audio_test_client.post(url=GET_CHOICES_URL, data=data) + response = audio_test_client.get(url=START_AUDIO_URL) + assert response.ok + + +def test_start_audio_sfx_off(audio_test_client): + data = {"music_on_off": "Off", "sfx_on_off": "Off"} + audio_test_client.post(url=GET_CHOICES_URL, data=data) + response = audio_test_client.get(url=START_AUDIO_URL) + assert response.ok