diff --git a/app/blueprint.py b/app/blueprint.py index 199c037b..36af7cbf 100644 --- a/app/blueprint.py +++ b/app/blueprint.py @@ -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 @@ -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) diff --git a/app/models/UserPreference.py b/app/models/UserPreference.py new file mode 100644 index 00000000..46c99aee --- /dev/null +++ b/app/models/UserPreference.py @@ -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()) \ No newline at end of file diff --git a/app/models/__init__.py b/app/models/__init__.py index ee51907f..0db76af7 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -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 diff --git a/app/models/data/user_preferences.json b/app/models/data/user_preferences.json new file mode 100644 index 00000000..44541c61 --- /dev/null +++ b/app/models/data/user_preferences.json @@ -0,0 +1,7 @@ +[ + { + "settings": { + "hideSimulationSettingErrors": false + } + } +] \ No newline at end of file diff --git a/app/routes/user_preference.py b/app/routes/user_preference.py new file mode 100644 index 00000000..88104f28 --- /dev/null +++ b/app/routes/user_preference.py @@ -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/") +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 \ No newline at end of file diff --git a/app/schemas/user_preference_schema.py b/app/schemas/user_preference_schema.py new file mode 100644 index 00000000..872990d9 --- /dev/null +++ b/app/schemas/user_preference_schema.py @@ -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() \ No newline at end of file diff --git a/app/services/user_preference_service.py b/app/services/user_preference_service.py new file mode 100644 index 00000000..711eef13 --- /dev/null +++ b/app/services/user_preference_service.py @@ -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!"} + \ No newline at end of file diff --git a/manage.py b/manage.py index 6db6685d..26067b22 100644 --- a/manage.py +++ b/manage.py @@ -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 @@ -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() @@ -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()