Skip to content
Merged
2 changes: 2 additions & 0 deletions app/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from app.routes.setting import blp as setting_blueprint
from app.routes.simulation import blp as simulation_blueprint
from app.routes.receive import blp as receive_blueprint
from app.routes.user_preference import blp as user_preference_blueprint


# Register Blueprint
Expand All @@ -27,3 +28,4 @@ def register_routing(app):
api.register_blueprint(auralization_blueprint)
api.register_blueprint(setting_blueprint)
api.register_blueprint(receive_blueprint)
api.register_blueprint(user_preference_blueprint)
28 changes: 28 additions & 0 deletions app/models/UserPreference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from datetime import datetime

from sqlalchemy import JSON

from app.db import db
from app.types import Setting, Status, ResourceType

class UserPreference(db.Model):
"""
Represents the database model for storing user-specific configurations.

Attributes
----------
id : int
The primary key for the preference record. Automatically incremented.
settings : dict
A JSON object containing various configuration settings for the user.
createdAt : str
The timestamp indicating when the preference record was created.
updatedAt : str
The timestamp indicating when the preference record was last updated.
"""
__tablename__ = "user_preferences"

id = db.Column(db.Integer, primary_key=True, autoincrement=True)
settings = db.Column(JSON, nullable=False)
createdAt = db.Column(db.String(), default=datetime.now())
updatedAt = db.Column(db.String(), default=datetime.now())
1 change: 1 addition & 0 deletions app/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
from app.models.Simulation import Simulation
from app.models.SimulationRun import SimulationRun
from app.models.Task import Task
from app.models.UserPreference import UserPreference
7 changes: 7 additions & 0 deletions app/models/data/user_preferences.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"settings": {
"hideSimulationSettingErrors": false
}
}
]
55 changes: 55 additions & 0 deletions app/routes/user_preference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from flask.views import MethodView
from flask_smorest import Blueprint

from app.schemas.user_preference_schema import (
UserPreferenceSchema,
UserPreferenceUpdateBodySchema,
)
from app.services import user_preference_service

blp = Blueprint("User Preference", __name__, description="User Preference API")

@blp.route("/user-preferences")
class UserPreference(MethodView):
"""
HTTP methods for handling collections of user preferences.
"""

@blp.response(200, UserPreferenceSchema(many=True))
def get(self):
"""
Retrieve a list of all user preferences.

Returns
-------
list of UserPreference
A list of user preference database objects matching the schema.
"""
return user_preference_service.get_all_user_preferences()

@blp.route("/user-preferences/<int:user_preference_id>")
class UserPreferenceDetail(MethodView):
"""
HTTP methods for handling operations on specific user preference records.
"""

@blp.arguments(UserPreferenceUpdateBodySchema)
@blp.response(200, UserPreferenceSchema)
def put(self, body_data, user_preference_id):
"""
Update an existing user preference configuration by its unique ID.

Parameters
----------
body_data : dict
The validated request body containing updated user preference data.
user_preference_id : int
The unique identifier of the user preference record to modify.

Returns
-------
UserPreference
The updated user preference database object matching the schema.
"""
result = user_preference_service.update_user_preference(user_preference_id, body_data)
return result
43 changes: 43 additions & 0 deletions app/schemas/user_preference_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from marshmallow import Schema, fields


class UserPreferenceSchema(Schema):
"""
Serialization and deserialization schema for the complete UserPreference model.

This schema is used to validate and format the full user preference data
when transferring it across application layers or API endpoints.

Attributes
----------
id : int
The unique identifier for the preference record.
settings : dict
The dictionary containing user-specific configuration settings.
createdAt : str
The timestamp indicating when the preference record was created.
updatedAt : str
The timestamp indicating when the preference record was last updated.
"""

id = fields.Integer()
settings = fields.Dict()
createdAt = fields.String()
updatedAt = fields.String()


class UserPreferenceUpdateBodySchema(Schema):
"""
Deserialization schema for validating user preference update payloads.

This schema is specifically used to validate incoming request bodies
when a user updates their configuration settings, ensuring only permissible
fields are processed.

Attributes
----------
settings : dict
The dictionary containing the updated user-specific configuration settings.
"""

settings = fields.Dict()
170 changes: 170 additions & 0 deletions app/services/user_preference_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import json
import logging
import os

from flask_smorest import abort
from sqlalchemy import asc

from app.db import db
from app.models import UserPreference
from config import app_dir
from datetime import datetime

# Create logger for this module
logger = logging.getLogger(__name__)


def get_all_user_preferences():
"""
Retrieve all user preference records from the database ordered by ID.

Returns
-------
list of UserPreference
A list of all user preference database model objects ordered ascending by ID.
"""
return UserPreference.query.order_by(asc(UserPreference.id)).all()


def create_new_user_preference(user_preference_data):
"""
Create and persist a new user preference record in the database.

Parameters
----------
user_preference_data : dict
A dictionary containing the field values required to instantiate
a new UserPreference model instance.

Returns
-------
UserPreference
The newly created and committed UserPreference database object.

Raises
------
HTTPException
Aborts with a 400 status code if a database transaction error occurs.
"""
new_user_preference = UserPreference(**user_preference_data)

try:
db.session.add(new_user_preference)
db.session.commit()

except Exception as ex:
db.session.rollback()
logger.error(f"Can not create a new user preference: {ex}")
abort(400, f"Can not create a new user preference: {ex}")

return new_user_preference


def update_user_preference(user_preference_id, user_preference_data):
"""
Update the settings and timestamp of an existing user preference record.

Parameters
----------
user_preference_id : int
The unique identifier of the user preference record to update.
user_preference_data : dict
A dictionary containing the updated configuration data, specifically
expecting a "settings" key.

Returns
-------
UserPreference
The updated and committed UserPreference database object.

Raises
------
HTTPException
Aborts with a 404 status code if the record is not found, or a 400
status code if a database transaction error occurs.
"""
user_preference = UserPreference.query.filter_by(id=user_preference_id).first()
if not user_preference:
abort(404, message="User preference doesn't exist, cannot update!")

try:
user_preference.settings = user_preference_data["settings"]
user_preference.updatedAt = datetime.now()
db.session.commit()
except Exception as ex:
db.session.rollback()
logger.error(f"Can not update! Error: {ex}")
abort(400, message=f"Can not update! Error: {ex}")

return user_preference


def get_user_preference_by_id(user_preference_id):
"""
Retrieve a specific user preference record by its unique ID.

Parameters
----------
user_preference_id : int
The unique identifier of the user preference record to fetch.

Returns
-------
UserPreference
The matching UserPreference database object.

Raises
------
HTTPException
Aborts with a 400 status code if no record matches the given ID.
"""
user_preference = UserPreference.query.filter_by(id=user_preference_id).first()
if not user_preference:
logger.error("User preference with id " + str(user_preference_id) + " does not exists!")
abort(400, "User preference doesn't exists!")
return user_preference


def insert_initial_user_preferences():
"""
Seed the database with initial user preference data from a JSON file.

If any records already exist in the user preferences table, the seeding
process is skipped entirely to prevent duplication.

Returns
-------
dict or None
A success message dictionary if initialization is performed,
or None if skipped.

Raises
------
HTTPException
Aborts with a 400 status code if a database or file system error occurs.
"""
user_preferences = get_all_user_preferences()
if len(user_preferences):
return
logger.info("Inserting initial user preferences")
with open(os.path.join(app_dir, "models", "data", "user_preferences.json")) as json_user_preferences:
initial_user_preferences = json.load(json_user_preferences)
try:
new_user_preferences = []
for user_preference in initial_user_preferences:
new_user_preferences.append(
UserPreference(
settings=user_preference["settings"],
)
)

db.session.add_all(new_user_preferences)
db.session.commit()

except Exception as ex:
db.session.rollback()
logger.error(f"Can not insert initial user preferences! Error: {ex}")
abort(400, f"Can not insert initial user preferences! Error: {ex}")

return {"message": "Initial user preferences added successfully!"}

4 changes: 3 additions & 1 deletion manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from passlib.hash import pbkdf2_sha256

from app.db import db
from app.services import auralization_service, material_service, setting_service
from app.services import auralization_service, material_service, setting_service, user_preference_service
from config import DefaultConfig


Expand Down Expand Up @@ -85,6 +85,7 @@ def create_db():
material_service.insert_initial_materials()
auralization_service.insert_initial_audios_examples()
setting_service.insert_initial_settings()
user_preference_service.insert_initial_user_preferences()
db.session.commit()


Expand All @@ -97,6 +98,7 @@ def reset_db():
material_service.insert_initial_materials()
auralization_service.insert_initial_audios_examples()
setting_service.insert_initial_settings()
user_preference_service.insert_initial_user_preferences()
db.session.commit()


Expand Down
Loading