From dea22c8ca1a90e61b44932b9f69c526544921197 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Jul 2025 09:18:50 +0000 Subject: [PATCH 01/16] Bump alembic from 1.16.2 to 1.16.4 in /server Bumps [alembic](https://github.com/sqlalchemy/alembic) from 1.16.2 to 1.16.4. - [Release notes](https://github.com/sqlalchemy/alembic/releases) - [Changelog](https://github.com/sqlalchemy/alembic/blob/main/CHANGES) - [Commits](https://github.com/sqlalchemy/alembic/commits) --- updated-dependencies: - dependency-name: alembic dependency-version: 1.16.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- server/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/requirements.txt b/server/requirements.txt index 59f207c42..8c3a79f4a 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -6,6 +6,6 @@ marshmallow-sqlalchemy==0.30.0 tornado-prometheus==0.1.2 bcrypt==4.3.0 anytree==2.13.0 -alembic==1.16.2 +alembic==1.16.4 marshmallow<4 pyjwt[crypto]==2.10.1 \ No newline at end of file From fbe1027d353581c98e7afe7dbb58fb5028afde53 Mon Sep 17 00:00:00 2001 From: Tim Bradgate Date: Sat, 23 Aug 2025 18:19:31 +0100 Subject: [PATCH 02/16] Add page up/down navigation to live view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements issue #710 by adding keyboard handlers for PageUp and PageDown keys in the live script view. Users can now jump between script pages quickly during live shows. Features: - PageUp/PageDown keys navigate between script pages - Only available to script leaders (maintains existing permission model) - Disabled during intervals (consistent with existing navigation) - Proper boundary checking to prevent navigation beyond loaded pages - Navigates to first line of target page for consistent positioning 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- client/src/views/show/ShowLiveView.vue | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/client/src/views/show/ShowLiveView.vue b/client/src/views/show/ShowLiveView.vue index 859380415..f1705760f 100644 --- a/client/src/views/show/ShowLiveView.vue +++ b/client/src/views/show/ShowLiveView.vue @@ -759,6 +759,8 @@ export default { // Process arrow keys if (event.key === 'ArrowUp' || event.key === 'ArrowDown') { this.handleKeyNavigation(event); + } else if (event.key === 'PageUp' || event.key === 'PageDown') { + this.handlePageNavigation(event); } else if (event.key === 'C') { this.handleCueEditToggle(event); } @@ -787,6 +789,28 @@ export default { const delta = event.key === 'ArrowDown' ? 1 : -1; this.navigateRelative(0, delta); }, + handlePageNavigation(event) { + // Only handle if we're the leader and not currently scrolling or starting an interval + if (!this.isScriptLeader || !this.initialLoad || this.isScrollingProgrammatically + || this.intervalTimerContext != null || this.CURRENT_SHOW_INTERVAL != null) return; + + event.preventDefault(); + + // Navigate by page increments + const isPageDown = event.key === 'PageDown'; + let targetPage = this.currentPage + (isPageDown ? 1 : -1); + + // Ensure we stay within valid page bounds + if (targetPage < 1) { + targetPage = 1; + } else if (targetPage > this.currentLoadedPage) { + // Don't navigate beyond loaded pages + return; + } + + // Navigate to the first line of the target page + this.navigateTo(targetPage, 0); + }, handleWheelNavigation(event) { // Only handle if we're the leader and not currently scrolling or starting an interval if (!this.isScriptLeader || !this.initialLoad || this.isScrollingProgrammatically From d5c9c40726d0e04824b6bc47027b402977d989d5 Mon Sep 17 00:00:00 2001 From: Tim Bradgate Date: Sun, 24 Aug 2025 03:08:40 +0100 Subject: [PATCH 03/16] Fix multiple submission issues in modal components (#721) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix multiple submission issue in ShowLiveView new cue modal - Add submittingNewCue state to prevent concurrent submissions - Disable OK button during submission - Add proper error handling with try/catch - Ensure modal closes after successful submission - Reset submission state in resetNewCueForm This fixes issue #696 where pressing enter in the new cue modal could create multiple cues due to lack of submission protection. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * Fix multiple submission issues in ScriptLineCueEditor modals - Add submittingNewCue, submittingEditCue, and deletingCue states - Disable modal buttons during operations to prevent concurrent requests - Add proper error handling with try/catch blocks - Ensure modals close after successful operations - Reset submission states in form reset methods This prevents multiple cue operations from being triggered by rapid form submissions or button clicks, complementing the ShowLiveView fix. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * Fix multiple submission issues in ConfigCast modals - Add submittingNewCast, submittingEditCast, and deletingCast states - Disable modal buttons and table actions during operations - Add proper error handling with try/catch blocks - Ensure modals close after successful operations - Reset submission states in form reset methods This prevents multiple cast member operations from being triggered by rapid form submissions or button clicks. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * Fix multiple submission issues in ConfigCharacters and ConfigCharacterGroups modals ConfigCharacters.vue: - Add submission state management for new/edit/delete operations - Disable modal buttons and table actions during operations - Add proper error handling and modal closure behavior ConfigCharacterGroups.vue: - Add submission state management for new/edit/delete group operations - Disable modal buttons and table actions during operations - Add proper error handling and modal closure behavior - Fix modal title from "Add New" to "Edit Character Group" Both files now follow the established modal submission protection pattern. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * Fix multiple submission issues in remaining config modals ConfigActs.vue: - Add submission state management for new/edit/delete act operations - Disable modal buttons and table actions during operations - Add proper error handling and modal closure behavior ConfigScenes.vue: - Add submission state management for new/edit/delete/first scene operations - Disable modal buttons and table actions during operations - Add proper error handling and modal closure behavior ConfigCues.vue: - Add submission state management for new/edit/delete cue type operations - Disable modal buttons and table actions during operations - Add proper error handling and modal closure behavior ConfigShow.vue: - Add submission state management for edit show operations - Disable action buttons during operations - Add proper error handling and modal closure behavior ConfigScript.vue: - Add submission state management for new revision/load/delete operations - Disable action buttons and table actions during operations - Add proper error handling and modal closure behavior All config files now follow the established modal submission protection pattern. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * Fix multiple submission issues in remaining modal components ConfigSystem.vue: - Add submission state management for show save/load operations - Disable modal buttons and load buttons during operations - Add proper error handling with try/catch blocks MicList.vue: - Add submission state management for new/edit/delete microphone operations - Disable modal buttons and delete buttons during operations - Add proper error handling and modal closure behavior - Fix logging import path StageDirectionStyles.vue (show config): - Add submission state management for new/edit/delete style operations - Disable modal buttons and table actions during operations - Add proper error handling and modal closure behavior - Fix bug in resetEditFormState method StageDirectionStyles.vue (user settings): - Add submission state management for new/edit/delete override operations - Disable modal buttons and table actions during operations - Add proper error handling and modal closure behavior All remaining modal components now follow the established submission protection pattern. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --------- Co-authored-by: Claude --- .idea/material_theme_project_new.xml | 10 ++ client/src/views/show/ShowLiveView.vue | 17 +++- client/src/views/show/config/ConfigActs.vue | 52 +++++++++- client/src/views/show/config/ConfigCast.vue | 52 +++++++++- .../show/config/ConfigCharacterGroups.vue | 54 ++++++++-- .../views/show/config/ConfigCharacters.vue | 52 +++++++++- client/src/views/show/config/ConfigCues.vue | 52 +++++++++- client/src/views/show/config/ConfigScenes.vue | 70 +++++++++++-- client/src/views/show/config/ConfigScript.vue | 55 +++++++++-- client/src/views/show/config/ConfigShow.vue | 19 +++- .../vue_components/config/ConfigSystem.vue | 99 +++++++++++++------ .../show/config/cues/ScriptLineCueEditor.vue | 56 +++++++++-- .../show/config/mics/MicList.vue | 61 +++++++++++- .../config/script/StageDirectionStyles.vue | 64 +++++++++++- .../user/settings/StageDirectionStyles.vue | 63 +++++++++++- 15 files changed, 677 insertions(+), 99 deletions(-) create mode 100644 .idea/material_theme_project_new.xml diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 000000000..f859fa2b4 --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/client/src/views/show/ShowLiveView.vue b/client/src/views/show/ShowLiveView.vue index f1705760f..f1a120906 100644 --- a/client/src/views/show/ShowLiveView.vue +++ b/client/src/views/show/ShowLiveView.vue @@ -200,6 +200,7 @@ :id="`new-cue-modal`" title="Add New Cue" size="md" + :ok-disabled="$v.newCueFormState.$invalid || submittingNewCue" @hidden="resetNewCueForm" @ok="onSubmitNewCue" > @@ -309,6 +310,7 @@ export default { ident: null, lineId: null, }, + submittingNewCue: false, }; }, validations: { @@ -1160,6 +1162,7 @@ export default { ident: null, lineId: null, }; + this.submittingNewCue = false; this.$nextTick(() => { this.$v.$reset(); @@ -1171,11 +1174,21 @@ export default { }, async onSubmitNewCue(event) { this.$v.newCueFormState.$touch(); - if (this.$v.newCueFormState.$anyError) { + if (this.$v.newCueFormState.$anyError || this.submittingNewCue) { event.preventDefault(); - } else { + return; + } + + this.submittingNewCue = true; + try { await this.ADD_NEW_CUE(this.newCueFormState); + this.$bvModal.hide('new-cue-modal'); this.resetNewCueForm(); + } catch (error) { + log.error('Error submitting new cue:', error); + event.preventDefault(); + } finally { + this.submittingNewCue = false; } }, ...mapActions(['GET_SHOW_SESSION_DATA', 'LOAD_SCRIPT_PAGE', 'GET_ACT_LIST', 'GET_SCENE_LIST', diff --git a/client/src/views/show/config/ConfigActs.vue b/client/src/views/show/config/ConfigActs.vue index 1a269acb5..66ab0c2e8 100644 --- a/client/src/views/show/config/ConfigActs.vue +++ b/client/src/views/show/config/ConfigActs.vue @@ -53,12 +53,14 @@ Edit Delete @@ -81,6 +83,7 @@ ref="new-act" title="Add New Act" size="md" + :ok-disabled="$v.newFormState.$invalid || submittingNewAct" @show="resetNewForm" @hidden="resetNewForm" @ok="onSubmitNew" @@ -137,6 +140,7 @@ ref="edit-act" title="Edit Act" size="md" + :ok-disabled="$v.editFormState.$invalid || submittingEditAct" @hidden="resetEditForm" @ok="onSubmitEdit" > @@ -199,6 +203,7 @@ diff --git a/client/src/vue_components/user/settings/ApiToken.vue b/client/src/vue_components/user/settings/ApiToken.vue new file mode 100644 index 000000000..d51926fd0 --- /dev/null +++ b/client/src/vue_components/user/settings/ApiToken.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/server/alembic_config/versions/e1a2b3c4d5e6_add_user_api_token.py b/server/alembic_config/versions/e1a2b3c4d5e6_add_user_api_token.py new file mode 100644 index 000000000..ce1e13cc7 --- /dev/null +++ b/server/alembic_config/versions/e1a2b3c4d5e6_add_user_api_token.py @@ -0,0 +1,36 @@ +"""Add user API token + +Revision ID: e1a2b3c4d5e6 +Revises: 8c78b9c89ee6 +Create Date: 2025-11-27 20:45:00.000000 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "e1a2b3c4d5e6" +down_revision: Union[str, None] = "8c78b9c89ee6" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("user", schema=None) as batch_op: + batch_op.add_column(sa.Column("api_token", sa.String(), nullable=True)) + batch_op.create_index("ix_user_api_token", ["api_token"], unique=False) + + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("user", schema=None) as batch_op: + batch_op.drop_index("ix_user_api_token") + batch_op.drop_column("api_token") + + # ### end Alembic commands ### diff --git a/server/controllers/api/auth.py b/server/controllers/api/auth.py index 0556a0414..32ca5ab4d 100644 --- a/server/controllers/api/auth.py +++ b/server/controllers/api/auth.py @@ -1,3 +1,4 @@ +import secrets from datetime import datetime, timezone import bcrypt @@ -272,3 +273,76 @@ class AuthHandler(BaseAPIController): def get(self): self.set_status(200) self.finish(self.current_user if self.current_user else {}) + + +@ApiRoute("auth/api-token/generate", ApiVersion.V1) +class ApiTokenGenerateController(BaseAPIController): + @api_authenticated + async def post(self): + """Generate a new API token for the authenticated user""" + with self.make_session() as session: + user = session.query(User).get(self.current_user["id"]) + if not user: + self.set_status(404) + await self.finish({"message": "User not found"}) + return + + # Generate a secure random token (plain text to return to user) + new_token = secrets.token_urlsafe(32) + + # Hash the token before storing (like passwords) + hashed_token = await IOLoop.current().run_in_executor( + None, bcrypt.hashpw, escape.utf8(new_token), bcrypt.gensalt() + ) + hashed_token = escape.to_unicode(hashed_token) + + user.api_token = hashed_token + session.commit() + + self.set_status(200) + await self.finish( + { + "message": "API token generated successfully", + "api_token": new_token, + } + ) + + +@ApiRoute("auth/api-token/revoke", ApiVersion.V1) +class ApiTokenRevokeController(BaseAPIController): + @api_authenticated + async def post(self): + """Revoke the API token for the authenticated user""" + with self.make_session() as session: + user = session.query(User).get(self.current_user["id"]) + if not user: + self.set_status(404) + await self.finish({"message": "User not found"}) + return + + if not user.api_token: + self.set_status(400) + await self.finish({"message": "No API token to revoke"}) + return + + user.api_token = None + session.commit() + + self.set_status(200) + await self.finish({"message": "API token revoked successfully"}) + + +@ApiRoute("auth/api-token", ApiVersion.V1) +class ApiTokenController(BaseAPIController): + @api_authenticated + def get(self): + """Check if the authenticated user has an API token""" + with self.make_session() as session: + user = session.query(User).get(self.current_user["id"]) + if not user: + self.set_status(404) + self.finish({"message": "User not found"}) + return + + self.set_status(200) + self.finish({"has_token": user.api_token is not None}) diff --git a/server/models/user.py b/server/models/user.py index 03727c879..f07b35446 100644 --- a/server/models/user.py +++ b/server/models/user.py @@ -18,6 +18,7 @@ class User(db.Model): is_admin = Column(Boolean()) last_login = Column(DateTime()) last_seen = Column(DateTime()) + api_token = Column(String(), nullable=True, index=True) class UserSettings(db.Model): diff --git a/server/schemas/schemas.py b/server/schemas/schemas.py index 9d6df4070..aac3af258 100644 --- a/server/schemas/schemas.py +++ b/server/schemas/schemas.py @@ -37,7 +37,7 @@ class Meta: model = User load_instance = True include_fk = True - exclude = ("password",) + exclude = ("password", "api_token") @schema diff --git a/server/test/test_auth_api.py b/server/test/test_auth_api.py index 971d11c74..d5e7cfcf7 100644 --- a/server/test/test_auth_api.py +++ b/server/test/test_auth_api.py @@ -167,3 +167,189 @@ def test_jwt_logout_revoke(self): "/api/v1/auth", method="GET", headers={"Authorization": f"Bearer {token}"} ) self.assertEqual(401, response.code) + + def test_api_token_generate(self): + # Create and login a user + self.fetch( + "/api/v1/auth/create", + method="POST", + body=escape.json_encode( + { + "username": "foobar", + "password": "password", + "is_admin": True, + } + ), + ) + + response = self.fetch( + "/api/v1/auth/login", + method="POST", + body=escape.json_encode({"username": "foobar", "password": "password"}), + ) + response_body = escape.json_decode(response.body) + token = response_body["access_token"] + + # Generate API token + response = self.fetch( + "/api/v1/auth/api-token/generate", + method="POST", + body=escape.json_encode({}), + headers={"Authorization": f"Bearer {token}"}, + ) + response_body = escape.json_decode(response.body) + self.assertEqual(200, response.code) + self.assertTrue("api_token" in response_body) + self.assertTrue(len(response_body["api_token"]) > 0) + + def test_api_token_authentication(self): + # Create and login a user + self.fetch( + "/api/v1/auth/create", + method="POST", + body=escape.json_encode( + { + "username": "foobar", + "password": "password", + "is_admin": True, + } + ), + ) + + response = self.fetch( + "/api/v1/auth/login", + method="POST", + body=escape.json_encode({"username": "foobar", "password": "password"}), + ) + response_body = escape.json_decode(response.body) + token = response_body["access_token"] + + # Generate API token + response = self.fetch( + "/api/v1/auth/api-token/generate", + method="POST", + body=escape.json_encode({}), + headers={"Authorization": f"Bearer {token}"}, + ) + response_body = escape.json_decode(response.body) + api_token = response_body["api_token"] + + # Use API token to authenticate + response = self.fetch( + "/api/v1/auth", method="GET", headers={"X-API-Key": api_token} + ) + self.assertEqual(200, response.code) + response_body = escape.json_decode(response.body) + self.assertEqual("foobar", response_body["username"]) + + def test_api_token_invalid(self): + # Try to use an invalid API token + response = self.fetch( + "/api/v1/auth", method="GET", headers={"X-API-Key": "invalid-token"} + ) + self.assertEqual(401, response.code) + + def test_api_token_revoke(self): + # Create and login a user + self.fetch( + "/api/v1/auth/create", + method="POST", + body=escape.json_encode( + { + "username": "foobar", + "password": "password", + "is_admin": True, + } + ), + ) + + response = self.fetch( + "/api/v1/auth/login", + method="POST", + body=escape.json_encode({"username": "foobar", "password": "password"}), + ) + response_body = escape.json_decode(response.body) + token = response_body["access_token"] + + # Generate API token + response = self.fetch( + "/api/v1/auth/api-token/generate", + method="POST", + body=escape.json_encode({}), + headers={"Authorization": f"Bearer {token}"}, + ) + response_body = escape.json_decode(response.body) + api_token = response_body["api_token"] + + # Verify API token works + response = self.fetch( + "/api/v1/auth", method="GET", headers={"X-API-Key": api_token} + ) + self.assertEqual(200, response.code) + + # Revoke API token + response = self.fetch( + "/api/v1/auth/api-token/revoke", + method="POST", + body=escape.json_encode({}), + headers={"Authorization": f"Bearer {token}"}, + ) + self.assertEqual(200, response.code) + + # Verify API token no longer works + response = self.fetch( + "/api/v1/auth", method="GET", headers={"X-API-Key": api_token} + ) + self.assertEqual(401, response.code) + + def test_api_token_get(self): + # Create and login a user + self.fetch( + "/api/v1/auth/create", + method="POST", + body=escape.json_encode( + { + "username": "foobar", + "password": "password", + "is_admin": True, + } + ), + ) + + response = self.fetch( + "/api/v1/auth/login", + method="POST", + body=escape.json_encode({"username": "foobar", "password": "password"}), + ) + response_body = escape.json_decode(response.body) + token = response_body["access_token"] + + # Check no token exists initially + response = self.fetch( + "/api/v1/auth/api-token", + method="GET", + headers={"Authorization": f"Bearer {token}"}, + ) + response_body = escape.json_decode(response.body) + self.assertEqual(200, response.code) + self.assertFalse(response_body["has_token"]) + + # Generate API token + response = self.fetch( + "/api/v1/auth/api-token/generate", + method="POST", + body=escape.json_encode({}), + headers={"Authorization": f"Bearer {token}"}, + ) + response_body = escape.json_decode(response.body) + api_token = response_body["api_token"] + + # Check that token exists (but can't retrieve it) + response = self.fetch( + "/api/v1/auth/api-token", + method="GET", + headers={"Authorization": f"Bearer {token}"}, + ) + response_body = escape.json_decode(response.body) + self.assertEqual(200, response.code) + self.assertTrue(response_body["has_token"]) diff --git a/server/test/test_utils.py b/server/test/test_utils.py index f5ec36dfc..f2f5d9427 100644 --- a/server/test/test_utils.py +++ b/server/test/test_utils.py @@ -39,4 +39,8 @@ def tearDown(self): table_inspect = inspect(table) models.db.metadata.remove(table_inspect.mapped_table) + # Dispose the database engine to ensure proper test isolation + # This forces a new in-memory database for each test + models.db.engine.dispose() + super().tearDown() diff --git a/server/utils/web/base_controller.py b/server/utils/web/base_controller.py index 2c7570718..77b8378ac 100644 --- a/server/utils/web/base_controller.py +++ b/server/utils/web/base_controller.py @@ -2,7 +2,9 @@ from typing import TYPE_CHECKING, Any, Awaitable, Optional +import bcrypt from tornado import escape, httputil +from tornado.ioloop import IOLoop from tornado.web import HTTPError, RequestHandler from tornado_sqlalchemy import SessionMixin @@ -36,14 +38,13 @@ async def prepare( show_schema = ShowSchema() user_schema = UserSchema() - # Extract JWT token from header - auth_header = self.request.headers.get("Authorization", "") - token = self.application.jwt_service.get_token_from_authorization_header( - auth_header - ) - with self.make_session() as session: - # If we have a token, try to authenticate with it + # First, try JWT authentication + auth_header = self.request.headers.get("Authorization", "") + token = self.application.jwt_service.get_token_from_authorization_header( + auth_header + ) + if token: is_revoked = await self.application.jwt_service.is_token_revoked(token) if is_revoked: @@ -55,6 +56,33 @@ async def prepare( if user: self.current_user = user_schema.dump(user) + # If not authenticated via JWT, try API token authentication + if not self.current_user: + api_key = self.request.headers.get("X-API-Key", "") + if api_key: + # Get all users with API tokens and check each one + users_with_tokens = ( + session.query(User).filter(User.api_token.isnot(None)).all() + ) + + authenticated_user = None + for user in users_with_tokens: + # Compare the provided token with the hashed token + token_matches = await IOLoop.current().run_in_executor( + None, + bcrypt.checkpw, + escape.utf8(api_key), + escape.utf8(user.api_token), + ) + if token_matches: + authenticated_user = user + break + + if authenticated_user: + self.current_user = user_schema.dump(authenticated_user) + else: + raise HTTPError(401, log_message="Invalid API key") + current_show = await self.application.digi_settings.get("current_show") if current_show: show = session.query(Show).get(current_show) From b72165ac0c18c1798eacb4f88917627938ee84fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 22:21:43 +0000 Subject: [PATCH 11/16] Bump alembic from 1.16.5 to 1.17.2 in /server Bumps [alembic](https://github.com/sqlalchemy/alembic) from 1.16.5 to 1.17.2. - [Release notes](https://github.com/sqlalchemy/alembic/releases) - [Changelog](https://github.com/sqlalchemy/alembic/blob/main/CHANGES) - [Commits](https://github.com/sqlalchemy/alembic/commits) --- updated-dependencies: - dependency-name: alembic dependency-version: 1.17.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- server/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/requirements.txt b/server/requirements.txt index 482ae283b..db42c3019 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -6,7 +6,7 @@ marshmallow-sqlalchemy>=1.4.0 tornado-prometheus==0.1.2 bcrypt==4.3.0 anytree==2.13.0 -alembic==1.16.5 +alembic==1.17.2 marshmallow<5 pyjwt[crypto]==2.10.1 setuptools==80.9.0 \ No newline at end of file From af40606ec0a5598899e597c34e74913a70787e3d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 22:31:44 +0000 Subject: [PATCH 12/16] Bump black from 25.1.0 to 25.11.0 in /server (#735) Bumps [black](https://github.com/psf/black) from 25.1.0 to 25.11.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/25.1.0...25.11.0) --- updated-dependencies: - dependency-name: black dependency-version: 25.11.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- server/test_requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/test_requirements.txt b/server/test_requirements.txt index 69e116ed0..61615d2e6 100644 --- a/server/test_requirements.txt +++ b/server/test_requirements.txt @@ -1,5 +1,5 @@ pytest<8.5 pytest-asyncio pylint==3.3.8 -black==25.1.0 +black==25.11.0 isort==5.13.2 \ No newline at end of file From 3aaa8946cf852b5ec60f994f85a399bf66ff3f68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Nov 2025 22:42:23 +0000 Subject: [PATCH 13/16] Bump pylint from 3.3.8 to 3.3.9 in /server (#731) Bumps [pylint](https://github.com/pylint-dev/pylint) from 3.3.8 to 3.3.9. - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v3.3.8...v3.3.9) --- updated-dependencies: - dependency-name: pylint dependency-version: 3.3.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- server/test_requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/test_requirements.txt b/server/test_requirements.txt index 61615d2e6..33bd85b8c 100644 --- a/server/test_requirements.txt +++ b/server/test_requirements.txt @@ -1,5 +1,5 @@ pytest<8.5 pytest-asyncio -pylint==3.3.8 +pylint==3.3.9 black==25.11.0 isort==5.13.2 \ No newline at end of file From d8f904283b2d9993c0118b3fd3e917677b2ea721 Mon Sep 17 00:00:00 2001 From: Tim Bradgate Date: Fri, 28 Nov 2025 22:50:54 +0000 Subject: [PATCH 14/16] Update npm packages --- client/package-lock.json | 293 +++++++++++++++++++-------------------- client/package.json | 12 +- 2 files changed, 151 insertions(+), 154 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 99e3dcf16..8f5df60de 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -12,7 +12,7 @@ "bootstrap-vue": "2.23.1", "bootswatch": "4.6.2", "contrast-color": "1.0.1", - "core-js": "3.44.0", + "core-js": "3.47.0", "deep-object-diff": "1.1.9", "jquery": "3.7.1", "lodash": "4.17.21", @@ -27,11 +27,11 @@ "vuex-persistedstate": "3.2.1" }, "devDependencies": { - "@babel/core": "7.28.0", - "@babel/eslint-parser": "7.28.0", - "@babel/preset-env": "7.28.0", + "@babel/core": "7.28.5", + "@babel/eslint-parser": "7.28.5", + "@babel/preset-env": "7.28.5", "@types/vuelidate": "^0.7.22", - "@vitejs/plugin-vue2": "2.3.3", + "@vitejs/plugin-vue2": "2.3.4", "eslint": "8.57.0", "eslint-config-airbnb-base": "15.0.0", "eslint-import-resolver-alias": "1.1.2", @@ -39,7 +39,7 @@ "eslint-plugin-vue": "^9.27.0", "eslint-plugin-vuejs-accessibility": "1.2.0", "node-sass": "7.0.3", - "sass": "1.89.2", + "sass": "1.94.2", "sass-loader": "13.3.3", "vite": "4.5.5" }, @@ -48,20 +48,6 @@ "npm": ">=10.0.0 <11" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -78,9 +64,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", - "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", "dev": true, "license": "MIT", "engines": { @@ -88,22 +74,22 @@ } }, "node_modules/@babel/core": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", - "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.28.0", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -119,9 +105,9 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.28.0.tgz", - "integrity": "sha512-N4ntErOlKvcbTt01rr5wj3y55xnIdx1ymrfIr8C2WnM1Y9glFgWaGDEULJIazOX3XM9NRzhfJ6zZnQ1sBNWU+w==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.28.5.tgz", + "integrity": "sha512-fcdRcWahONYo+JRnJg1/AekOacGvKx12Gu0qXJXFi2WBqQA1i7+O5PaxRB7kxE/Op94dExnCiiar6T09pvdHpA==", "dev": true, "license": "MIT", "dependencies": { @@ -138,14 +124,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", - "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -185,18 +171,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", + "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "engines": { @@ -252,14 +238,14 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -280,15 +266,15 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -380,9 +366,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -414,26 +400,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.0" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -443,14 +429,14 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", - "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -510,14 +496,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", - "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -657,9 +643,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz", - "integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", + "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", "dev": true, "license": "MIT", "dependencies": { @@ -690,13 +676,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", - "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { @@ -707,9 +693,9 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.0.tgz", - "integrity": "sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", + "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", "dev": true, "license": "MIT", "dependencies": { @@ -718,7 +704,7 @@ "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/traverse": "^7.28.4" }, "engines": { "node": ">=6.9.0" @@ -745,14 +731,14 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", - "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -845,9 +831,9 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", - "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", + "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", "dev": true, "license": "MIT", "dependencies": { @@ -944,9 +930,9 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", - "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", + "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", "dev": true, "license": "MIT", "dependencies": { @@ -1010,16 +996,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", - "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", + "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1111,9 +1097,9 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz", - "integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", + "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", "dev": true, "license": "MIT", "dependencies": { @@ -1121,7 +1107,7 @@ "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.28.0", "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.0" + "@babel/traverse": "^7.28.4" }, "engines": { "node": ">=6.9.0" @@ -1164,9 +1150,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", - "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", + "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1248,9 +1234,9 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.0.tgz", - "integrity": "sha512-LOAozRVbqxEVjSKfhGnuLoE4Kz4Oc5UJzuvFUhSsQzdCdaAQu06mG8zDv2GFSerM62nImUZ7K92vxnQcLSDlCQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", + "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", "dev": true, "license": "MIT", "dependencies": { @@ -1445,21 +1431,21 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.0.tgz", - "integrity": "sha512-VmaxeGOwuDqzLl5JUkIRM1X2Qu2uKGxHEQWh+cvvbl7JuJRgKGJSfsEF/bUaxFhJl/XAyxBe7q7qSuTbKFuCyg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz", + "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.0", + "@babel/compat-data": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.27.1", "@babel/plugin-syntax-import-attributes": "^7.27.1", @@ -1468,42 +1454,42 @@ "@babel/plugin-transform-async-generator-functions": "^7.28.0", "@babel/plugin-transform-async-to-generator": "^7.27.1", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.0", + "@babel/plugin-transform-block-scoping": "^7.28.5", "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.27.1", - "@babel/plugin-transform-classes": "^7.28.0", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.4", "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.27.1", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.0", - "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-exponentiation-operator": "^7.28.5", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.27.1", "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.28.5", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-rest-spread": "^7.28.4", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.27.1", "@babel/plugin-transform-private-property-in-object": "^7.27.1", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.0", + "@babel/plugin-transform-regenerator": "^7.28.4", "@babel/plugin-transform-regexp-modifiers": "^7.27.1", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", @@ -1560,18 +1546,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", - "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.0", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.0", + "@babel/types": "^7.28.5", "debug": "^4.3.1" }, "engines": { @@ -1579,13 +1565,13 @@ } }, "node_modules/@babel/types": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.0.tgz", - "integrity": "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -2126,6 +2112,17 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -2724,16 +2721,16 @@ "license": "ISC" }, "node_modules/@vitejs/plugin-vue2": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue2/-/plugin-vue2-2.3.3.tgz", - "integrity": "sha512-qexY6+bbwY8h0AZerzUuGywNTi0cLOkbiSbggr0R3WEW95iB2hblQFyv4MAkkc2vm4gZN1cO5kzT1Kp6xlVzZw==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue2/-/plugin-vue2-2.3.4.tgz", + "integrity": "sha512-LgqtRRedJb1KdmgcllwGX0gtlPvOvtR6pITXmqxGwQhBZaAysg0Hd7wvj3sjCsj4+PENWsqS7O+ceYSOgJ+H9g==", "dev": true, "license": "MIT", "engines": { "node": "^14.18.0 || >= 16.0.0" }, "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "vue": "^2.7.0-0" } }, @@ -3830,9 +3827,9 @@ "license": "MIT" }, "node_modules/core-js": { - "version": "3.44.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.44.0.tgz", - "integrity": "sha512-aFCtd4l6GvAXwVEh3XbbVqJGHDJt0OZRa+5ePGx3LLwi12WfexqQxcsohb2wgsa/92xtl19Hd66G/L+TaAxDMw==", + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", + "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -8153,9 +8150,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.89.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.2.tgz", - "integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==", + "version": "1.94.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.94.2.tgz", + "integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==", "dev": true, "license": "MIT", "dependencies": { diff --git a/client/package.json b/client/package.json index 551c2172c..e030d9a56 100644 --- a/client/package.json +++ b/client/package.json @@ -17,7 +17,7 @@ "bootstrap-vue": "2.23.1", "bootswatch": "4.6.2", "contrast-color": "1.0.1", - "core-js": "3.44.0", + "core-js": "3.47.0", "deep-object-diff": "1.1.9", "jquery": "3.7.1", "lodash": "4.17.21", @@ -32,11 +32,11 @@ "vuex-persistedstate": "3.2.1" }, "devDependencies": { - "@babel/core": "7.28.0", - "@babel/eslint-parser": "7.28.0", - "@babel/preset-env": "7.28.0", + "@babel/core": "7.28.5", + "@babel/eslint-parser": "7.28.5", + "@babel/preset-env": "7.28.5", "@types/vuelidate": "^0.7.22", - "@vitejs/plugin-vue2": "2.3.3", + "@vitejs/plugin-vue2": "2.3.4", "eslint": "8.57.0", "eslint-config-airbnb-base": "15.0.0", "eslint-import-resolver-alias": "1.1.2", @@ -44,7 +44,7 @@ "eslint-plugin-vue": "^9.27.0", "eslint-plugin-vuejs-accessibility": "1.2.0", "node-sass": "7.0.3", - "sass": "1.89.2", + "sass": "1.94.2", "sass-loader": "13.3.3", "vite": "4.5.5" }, From 36f135ce50e94cd376a3c3c735f825cb1a8d30b2 Mon Sep 17 00:00:00 2001 From: Tim Bradgate Date: Fri, 28 Nov 2025 22:53:52 +0000 Subject: [PATCH 15/16] Bump version to 0.18.0 --- .idea/copilot.data.migration.agent.xml | 6 ++++++ .idea/copilot.data.migration.ask.xml | 6 ++++++ .idea/copilot.data.migration.edit.xml | 6 ++++++ client/package-lock.json | 4 ++-- client/package.json | 2 +- 5 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 .idea/copilot.data.migration.agent.xml create mode 100644 .idea/copilot.data.migration.ask.xml create mode 100644 .idea/copilot.data.migration.edit.xml diff --git a/.idea/copilot.data.migration.agent.xml b/.idea/copilot.data.migration.agent.xml new file mode 100644 index 000000000..4ea72a911 --- /dev/null +++ b/.idea/copilot.data.migration.agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.ask.xml b/.idea/copilot.data.migration.ask.xml new file mode 100644 index 000000000..7ef04e2ea --- /dev/null +++ b/.idea/copilot.data.migration.ask.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.edit.xml b/.idea/copilot.data.migration.edit.xml new file mode 100644 index 000000000..8648f9401 --- /dev/null +++ b/.idea/copilot.data.migration.edit.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index 8f5df60de..8dcc8435b 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "client", - "version": "0.17.10", + "version": "0.18.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "client", - "version": "0.17.10", + "version": "0.18.0", "dependencies": { "bootstrap": "4.6.2", "bootstrap-vue": "2.23.1", diff --git a/client/package.json b/client/package.json index e030d9a56..74feb636c 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "client", - "version": "0.17.10", + "version": "0.18.0", "private": true, "scripts": { "build": "vite build", From 63bac573f3de0d6c31fe2b73756614382d0dfd9d Mon Sep 17 00:00:00 2001 From: Tim Bradgate Date: Fri, 28 Nov 2025 23:01:07 +0000 Subject: [PATCH 16/16] feat(tooling): Add pyproject.toml for consolidated Python tool configuration (#741) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidate Python tool configurations into a single pyproject.toml file while preserving existing code formatting standards. Key improvements: - Single source of truth for tool configurations - Dynamic dependency loading from requirements.txt files - Pytest pythonpath configuration enables running `pytest` directly - No code reformatting required - matches existing standards Technical changes: - Created pyproject.toml with: - Black: line-length=88 (default, matches existing formatting) - isort: profile=black, line-length=88 - Pylint: migrated all settings from .pylintrc (max-line-length=120) - Pytest: pythonpath=["."] fixes import resolution - Removed .pylintrc (configuration moved to pyproject.toml) Benefits: - Run `pytest` directly without `python -m pytest` workaround - All tool configs in one standardized location - Modern Python packaging standards (PEP 517/518) - No configuration duplication 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude --- server/.pylintrc | 35 ------------ server/pyproject.toml | 127 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 35 deletions(-) delete mode 100644 server/.pylintrc create mode 100644 server/pyproject.toml diff --git a/server/.pylintrc b/server/.pylintrc deleted file mode 100644 index a4b01e8e2..000000000 --- a/server/.pylintrc +++ /dev/null @@ -1,35 +0,0 @@ -[MASTER] -init-hook="from pylint.config import find_default_config_files; import sys; sys.path.append(next(find_default_config_files()).parent.as_posix())" -ignore-paths=^alembic_config/.*$,^test/.*$ - -[MESSAGES CONTROL] -disable= - logging-fstring-interpolation, - missing-module-docstring, - missing-class-docstring, - missing-function-docstring, - too-few-public-methods, - duplicate-code, - too-many-return-statements, - too-many-branches, - too-many-statements, - unnecessary-lambda, - too-many-locals, - too-many-nested-blocks, - too-many-arguments, - unnecessary-dunder-call, - broad-exception-raised, - broad-exception-caught, - fixme - -[FORMAT] -max-line-length=120 - -[DESIGN] -max-args=15 -max-locals=20 -max-returns=20 -max-branches=20 -max-statements=50 -max-attributes=20 -max-positional-arguments=15 diff --git a/server/pyproject.toml b/server/pyproject.toml new file mode 100644 index 000000000..0163046ce --- /dev/null +++ b/server/pyproject.toml @@ -0,0 +1,127 @@ +# ============================================================================ +# DigiScript Server - Python Project Configuration +# ============================================================================ +# This file consolidates configuration for all Python development tools. +# Dependencies are dynamically read from requirements.txt files. +# ============================================================================ + +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "digiscript-server" +version = "0.1.0" +description = "DigiScript server - Digital script management for theatrical shows" +readme = "../README.md" +requires-python = ">=3.13" +# Dependencies are dynamically loaded from requirements.txt +dynamic = ["dependencies", "optional-dependencies"] + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} +optional-dependencies.test = {file = ["test_requirements.txt"]} + +# ============================================================================ +# PYTEST CONFIGURATION +# ============================================================================ +[tool.pytest.ini_options] +# Add the server directory to Python path so imports work correctly +# This allows running `pytest` directly instead of `python -m pytest` +pythonpath = ["."] + +# Test discovery patterns +testpaths = ["test"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] + +# Asyncio configuration for pytest-asyncio +asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "function" + +# Output options +addopts = [ + "-v", # Verbose output + "--tb=short", # Shorter traceback format + "--strict-markers", # Ensure all markers are defined +] + +# Markers +markers = [ + "asyncio: mark test as async", +] + +# ============================================================================ +# BLACK CONFIGURATION +# ============================================================================ +[tool.black] +# Use Black's default line-length of 88 to match existing codebase formatting +line-length = 88 +target-version = ['py313'] +include = '\.pyi?$' +extend-exclude = ''' +/( + # Exclude alembic migrations + | alembic_config/versions +)/ +''' + +# ============================================================================ +# ISORT CONFIGURATION +# ============================================================================ +[tool.isort] +profile = "black" +# Match Black's line length of 88 +line_length = 88 +skip_gitignore = true +extend_skip = ["alembic_config/versions"] +known_first_party = ["digi_server", "models", "controllers", "utils", "schemas", "rbac", "registry"] + +# ============================================================================ +# PYLINT CONFIGURATION +# ============================================================================ +[tool.pylint.main] +# Add current directory to path for imports +init-hook = "from pylint.config import find_default_config_files; import sys; sys.path.append(next(find_default_config_files()).parent.as_posix())" + +# Ignore patterns +ignore-paths = [ + "^alembic_config/.*$", + "^test/.*$", +] + +[tool.pylint."messages control"] +disable = [ + "logging-fstring-interpolation", + "missing-module-docstring", + "missing-class-docstring", + "missing-function-docstring", + "too-few-public-methods", + "duplicate-code", + "too-many-return-statements", + "too-many-branches", + "too-many-statements", + "unnecessary-lambda", + "too-many-locals", + "too-many-nested-blocks", + "too-many-arguments", + "unnecessary-dunder-call", + "broad-exception-raised", + "broad-exception-caught", + "fixme", +] + +[tool.pylint.format] +# Note: Pylint's max-line-length is 120 but Black enforces 88 +# This is intentional - Pylint checks logic, Black formats code +max-line-length = 120 + +[tool.pylint.design] +max-args = 15 +max-locals = 20 +max-returns = 20 +max-branches = 20 +max-statements = 50 +max-attributes = 20 +max-positional-arguments = 15