You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We reviewed changes in e95ba09...4af4f70 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.
Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.
Previous review found 53 inline comments (DeepSource issues) across multiple files. This incremental change only adds a Caps Lock detection feature to the PasswordInput component.
The reason will be displayed to describe this comment to others. Learn more.
Access to a protected member _mapping of a client class
Accessing a protected member (a member prefixed with _) of a class from outside that class is not recommended, since the creator of that class did not intend this member to be exposed. If accesing this attribute outside of the class is absolutely needed, refactor it such that it becomes part of the public interface of the class.
The reason will be displayed to describe this comment to others. Learn more.
Access to a protected member _mapping of a client class
Accessing a protected member (a member prefixed with _) of a class from outside that class is not recommended, since the creator of that class did not intend this member to be exposed. If accesing this attribute outside of the class is absolutely needed, refactor it such that it becomes part of the public interface of the class.
The reason will be displayed to describe this comment to others. Learn more.
`_register_manual_and_dashboard_routes` has a cyclomatic complexity of 19 with "high" risk
A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.
The reason will be displayed to describe this comment to others. Learn more.
`_create_inventory_items_from_order` has a cyclomatic complexity of 16 with "high" risk
A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.
The reason will be displayed to describe this comment to others. Learn more.
`_validate_stock_in_order` has a cyclomatic complexity of 16 with "high" risk
A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.
The reason will be displayed to describe this comment to others. Learn more.
`login` has a cyclomatic complexity of 17 with "high" risk
A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.
The reason will be displayed to describe this comment to others. Learn more.
Avoid revoking active SSE clients on self session refresh
When /users/me/sessions/refresh rotates the token hash, this code publishes a session-revoked signal for the old hash immediately. Any other tab that still has an SSE stream on that hash receives auth.invalid and the frontend invalidation path force-logs the user out, so a normal “refresh session” action in one tab can unexpectedly sign out active tabs. This should not emit a hard revocation for same-user refresh without a reconnect/grace flow.
The reason will be displayed to describe this comment to others. Learn more.
Exempt docs/openapi routes from unauthenticated redirect
The redirect bypass only allows /api, /login, /robots.txt, and /health. In development-like environments where FastAPI docs are enabled (/docs, /redoc, /openapi.json), requests are redirected to /login before reaching the docs handlers, effectively disabling API docs access. Add docs/openapi paths to the bypass list (or gate this middleware by environment) to avoid breaking local/debug workflows.
The reason will be displayed to describe this comment to others. Learn more.
18 issues found across 154 files
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="app/services/session_service.py">
<violation number="1" location="app/services/session_service.py:356">
P2: `_create_user_session` is dead code — it is defined but never called. The login flow now uses `stage_create_or_refresh_user_session` with explicit post-commit side effects. Remove this function to avoid maintenance burden on an unused code path.</violation>
</file>
<file name="app/models/common_shelf_operation_log.py">
<violation number="1" location="app/models/common_shelf_operation_log.py:54">
P1: Using `ondelete="CASCADE"` on an audit log table means deleting a user will silently destroy their entire operation log history. Other operation log models in this codebase (`reagent_order_operation_log`, `consumable_order_operation_log`) use `ondelete="SET NULL"` with `Optional[int]` to preserve audit trails. This field should follow the same pattern.</violation>
</file>
<file name="README.md">
<violation number="1" location="README.md:23">
P2: The `开发约定` section was removed but its TOC entry was not. This leaves a broken anchor link in the Table of Contents. Remove the orphaned `- [开发约定](#开发约定)` entry.</violation>
</file>
<file name="app/models/common_shelf.py">
<violation number="1" location="app/models/common_shelf.py:154">
P2: `CommonShelfManualCreate` is missing `model_config = ConfigDict(extra="forbid")`, unlike every other request model in this file. This allows clients to send unexpected extra fields without error, which is inconsistent and can mask typos or malicious payloads.</violation>
</file>
<file name="app/api/events.py">
<violation number="1" location="app/api/events.py:106">
P1: Blocking synchronous DB call inside an async generator will stall the event loop. `is_token_session_active` can fall through the Redis cache and execute `with Session(engine) as db`, which is synchronous I/O. Wrap this in `await asyncio.to_thread(...)` (or `loop.run_in_executor`) so the revalidation doesn't block all concurrent SSE streams and other async handlers.</violation>
</file>
<file name="app/core/auth.py">
<violation number="1" location="app/core/auth.py:119">
P2: `is_token_session_active` does not sync session back to Redis after a successful DB fallback. Unlike `resolve_current_session` which calls `sync_session_cache` on cache miss, this path returns `True` without repopulating the cache. Since SSE connections revalidate every 15 seconds, a cold cache causes a repeated DB JOIN query for the entire connection lifetime.</violation>
</file>
<file name="app/services/inventory_operation_logger.py">
<violation number="1" location="app/services/inventory_operation_logger.py:81">
P2: `status` is never included in the snapshot even though `SNAPSHOT_KEY_MAP` defines `"st" → "status"` and the `Inventory` model has a `status: InventoryStatus` field. Audit log entries will not capture whether the item was `in_stock`, `borrowed`, `consumed`, etc. at the time of the operation.</violation>
</file>
<file name="app/api/user_logs.py">
<violation number="1" location="app/api/user_logs.py:151">
P1: `int(None)` raises `TypeError`, which is not caught. If the payload is valid JSON but lacks `uid` or `exp`, `payload.get(...)` returns `None` and this will bubble up as a 500 instead of being treated as an invalid token. Add `TypeError` to the except clause.</violation>
</file>
<file name="app/services/common_shelf_queries.py">
<violation number="1" location="app/services/common_shelf_queries.py:410">
P2: The inline field list in the 'all' fallback duplicates `CHEMICAL_NAME_MAP_SQL_FIELD_MAP["name"] + CHEMICAL_NAME_MAP_SQL_FIELD_MAP["alias"]`. If a column is added to the map but not here, "all"-field searches silently miss it. Derive the list from the map constant instead.</violation>
</file>
<file name="app/database.py">
<violation number="1" location="app/database.py:173">
P3: `SQLITE_INVENTORY_OPERATION_LOG_INDEX_UPGRADES` is defined but never used anywhere. All five indexes it declares already exist in `SQLITE_PERFORMANCE_FILTER_SORT_INDEX_UPGRADES`, so this constant is dead code that could mislead future maintainers into thinking it serves a separate purpose.</violation>
</file>
<file name="app/models/inventory_operation_log.py">
<violation number="1" location="app/models/inventory_operation_log.py:72">
P1: Audit log uses `CASCADE` on `operator_id` — deleting a user will destroy their inventory operation log history. Every other operation log model in the codebase uses `ondelete="SET NULL"` with `Optional[int]` to preserve audit records. Apply the same pattern here.</violation>
</file>
<file name="app/main.py">
<violation number="1" location="app/main.py:197">
P2: Passing `background_tasks=None` causes `resolve_current_session` to run the activity-refresh task synchronously (DB reads + writes) inside this async middleware, blocking the event loop. Since only auth verification is needed here—not session activity tracking—consider using a lighter-weight auth check that skips the activity refresh, or explicitly suppress the refresh.</violation>
</file>
<file name="app/api/user_sessions.py">
<violation number="1" location="app/api/user_sessions.py:113">
P1: Returning 401 with `SESSION_EXPIRED` auth-error headers when the *target* session (not the caller's auth session) is expired will mislead the frontend into treating this as an authentication failure. Since the caller is authenticated via `get_current_session`, this should be a 404 or 410 to indicate the target session is no longer valid.</violation>
</file>
<file name="docker/nginx/nginx.conf">
<violation number="1" location="docker/nginx/nginx.conf:26">
P3: Add `server_tokens off;` in the `http` block to suppress the nginx version from response headers and error pages. This is a standard security hardening measure for production deployments.</violation>
</file>
<file name="frontend/scripts/lib-assets.mjs">
<violation number="1" location="frontend/scripts/lib-assets.mjs:88">
P2: The `catch` block swallows the inner "not a file" error thrown when `isFile()` is false, replacing it with a misleading "missing source file" message. Re-throw known errors or check the error type before replacing.</violation>
</file>
<file name="app/services/common_shelf_creation.py">
<violation number="1" location="app/services/common_shelf_creation.py:207">
P2: Missing positive-value check on `order.initial_quantity` — a negative value will produce an invalid specification (e.g. "-5 mL") and be persisted to `CommonShelf`. The manual-create path validates `value > 0` via `parse_specification`, but this order-based path does not.</violation>
</file>
<file name="app/services/inventory_import_preview_sessions.py">
<violation number="1" location="app/services/inventory_import_preview_sessions.py:199">
P1: The `token` is embedded into a Redis `SCAN` glob pattern without escaping metacharacters (`*`, `?`, `[`). A token containing `*` would match and delete all preview session keys across all users. Escape the token or scope the key by `user_id` (as `consume_*` does) to build an exact key for `DELETE` instead of scanning.</violation>
</file>
<file name="app/services/internal_code.py">
<violation number="1" location="app/services/internal_code.py:111">
P1: The parameter dict is passed as a positional argument to `session.exec()`, but SQLModel's `Session.exec()` declares `params` as keyword-only. This would raise a `TypeError` at runtime. Use `params={...}` instead. The same issue exists in `_reserve_sequence_range` below.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
The reason will be displayed to describe this comment to others. Learn more.
P1: Blocking synchronous DB call inside an async generator will stall the event loop. is_token_session_active can fall through the Redis cache and execute with Session(engine) as db, which is synchronous I/O. Wrap this in await asyncio.to_thread(...) (or loop.run_in_executor) so the revalidation doesn't block all concurrent SSE streams and other async handlers.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/events.py, line 106:
<comment>Blocking synchronous DB call inside an async generator will stall the event loop. `is_token_session_active` can fall through the Redis cache and execute `with Session(engine) as db`, which is synchronous I/O. Wrap this in `await asyncio.to_thread(...)` (or `loop.run_in_executor`) so the revalidation doesn't block all concurrent SSE streams and other async handlers.</comment>
<file context>
@@ -84,10 +94,24 @@ async def event_generator():
+
+ now_utc = get_utc_now()
+ if now_utc >= next_revalidate_at:
+ if not is_token_session_active(current_session.token_hash, client_ip=client_ip):
+ sse_manager.notify_session_revoked(
+ token_hash=current_session.token_hash,
</file context>
The reason will be displayed to describe this comment to others. Learn more.
P1: int(None) raises TypeError, which is not caught. If the payload is valid JSON but lacks uid or exp, payload.get(...) returns None and this will bubble up as a 500 instead of being treated as an invalid token. Add TypeError to the except clause.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/user_logs.py, line 151:
<comment>`int(None)` raises `TypeError`, which is not caught. If the payload is valid JSON but lacks `uid` or `exp`, `payload.get(...)` returns `None` and this will bubble up as a 500 instead of being treated as an invalid token. Add `TypeError` to the except clause.</comment>
<file context>
@@ -81,27 +119,40 @@ def _record_logs_token_request(admin_user_id: int) -> None:
- expires_at = int(parts[1])
return (user_id, expires_at)
- except (ValueError, IndexError):
+ except (ValueError, IndexError, json.JSONDecodeError, UnicodeDecodeError):
return None
</file context>
The reason will be displayed to describe this comment to others. Learn more.
P1: Audit log uses CASCADE on operator_id — deleting a user will destroy their inventory operation log history. Every other operation log model in the codebase uses ondelete="SET NULL" with Optional[int] to preserve audit records. Apply the same pattern here.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/models/inventory_operation_log.py, line 72:
<comment>Audit log uses `CASCADE` on `operator_id` — deleting a user will destroy their inventory operation log history. Every other operation log model in the codebase uses `ondelete="SET NULL"` with `Optional[int]` to preserve audit records. Apply the same pattern here.</comment>
<file context>
@@ -0,0 +1,106 @@
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ inventory_id: int = Field(index=False)
+ operator_id: int = Field(
+ foreign_key="users.id",
+ ondelete="CASCADE",
</file context>
The reason will be displayed to describe this comment to others. Learn more.
P1: Returning 401 with SESSION_EXPIRED auth-error headers when the target session (not the caller's auth session) is expired will mislead the frontend into treating this as an authentication failure. Since the caller is authenticated via get_current_session, this should be a 404 or 410 to indicate the target session is no longer valid.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/user_sessions.py, line 113:
<comment>Returning 401 with `SESSION_EXPIRED` auth-error headers when the *target* session (not the caller's auth session) is expired will mislead the frontend into treating this as an authentication failure. Since the caller is authenticated via `get_current_session`, this should be a 404 or 410 to indicate the target session is no longer valid.</comment>
<file context>
@@ -93,76 +86,86 @@ def delete_all_sessions(
raise HTTPException(
- status_code=status.HTTP_404_NOT_FOUND,
- detail="No active session"
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Session has been revoked, please login again",
+ headers={
</file context>
The reason will be displayed to describe this comment to others. Learn more.
P2: Passing background_tasks=None causes resolve_current_session to run the activity-refresh task synchronously (DB reads + writes) inside this async middleware, blocking the event loop. Since only auth verification is needed here—not session activity tracking—consider using a lighter-weight auth check that skips the activity refresh, or explicitly suppress the refresh.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/main.py, line 197:
<comment>Passing `background_tasks=None` causes `resolve_current_session` to run the activity-refresh task synchronously (DB reads + writes) inside this async middleware, blocking the event loop. Since only auth verification is needed here—not session activity tracking—consider using a lighter-weight auth check that skips the activity refresh, or explicitly suppress the refresh.</comment>
<file context>
@@ -147,6 +176,28 @@ def _should_skip_https_redirect(path: str) -> bool:
+
+def _is_authenticated_request(request: Request) -> bool:
+ with Session(engine) as db:
+ resolve_current_session(request=request, background_tasks=None, db=db)
+ return True
+
</file context>
The reason will be displayed to describe this comment to others. Learn more.
P2: The catch block swallows the inner "not a file" error thrown when isFile() is false, replacing it with a misleading "missing source file" message. Re-throw known errors or check the error type before replacing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/scripts/lib-assets.mjs, line 88:
<comment>The `catch` block swallows the inner "not a file" error thrown when `isFile()` is false, replacing it with a misleading "missing source file" message. Re-throw known errors or check the error type before replacing.</comment>
<file context>
@@ -0,0 +1,148 @@
+ if (!sourceStat.isFile()) {
+ throw new Error(`${definition.sourceName} 不是文件`)
+ }
+ } catch {
+ throw new Error(`缺少源资源文件: ${sourcePath}`)
+ }
</file context>
The reason will be displayed to describe this comment to others. Learn more.
P2: Missing positive-value check on order.initial_quantity — a negative value will produce an invalid specification (e.g. "-5 mL") and be persisted to CommonShelf. The manual-create path validates value > 0 via parse_specification, but this order-based path does not.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/services/common_shelf_creation.py, line 207:
<comment>Missing positive-value check on `order.initial_quantity` — a negative value will produce an invalid specification (e.g. "-5 mL") and be persisted to `CommonShelf`. The manual-create path validates `value > 0` via `parse_specification`, but this order-based path does not.</comment>
<file context>
@@ -0,0 +1,300 @@
+ """Create common shelf rows from a common-public reagent order."""
+ if order.quantity is None or order.quantity <= 0:
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid order quantity")
+ if order.initial_quantity is None or order.unit is None:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
</file context>
The reason will be displayed to describe this comment to others. Learn more.
P3: SQLITE_INVENTORY_OPERATION_LOG_INDEX_UPGRADES is defined but never used anywhere. All five indexes it declares already exist in SQLITE_PERFORMANCE_FILTER_SORT_INDEX_UPGRADES, so this constant is dead code that could mislead future maintainers into thinking it serves a separate purpose.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/database.py, line 173:
<comment>`SQLITE_INVENTORY_OPERATION_LOG_INDEX_UPGRADES` is defined but never used anywhere. All five indexes it declares already exist in `SQLITE_PERFORMANCE_FILTER_SORT_INDEX_UPGRADES`, so this constant is dead code that could mislead future maintainers into thinking it serves a separate purpose.</comment>
<file context>
@@ -118,6 +170,14 @@ def get_db() -> Generator[Session, None, None]:
+ SQLITE_PERFORMANCE_FILTER_SORT_INDEX_UPGRADES
)
+SQLITE_INVENTORY_OPERATION_LOG_INDEX_UPGRADES: tuple[str, ...] = (
+ "CREATE INDEX IF NOT EXISTS ix_inventory_operation_log_operator_created_at ON inventory_operation_log (operator_id, created_at DESC)",
+ "CREATE INDEX IF NOT EXISTS ix_inventory_operation_log_action_created_at ON inventory_operation_log (action, created_at DESC)",
</file context>
The reason will be displayed to describe this comment to others. Learn more.
P3: Add server_tokens off; in the http block to suppress the nginx version from response headers and error pages. This is a standard security hardening measure for production deployments.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docker/nginx/nginx.conf, line 26:
<comment>Add `server_tokens off;` in the `http` block to suppress the nginx version from response headers and error pages. This is a standard security hardening measure for production deployments.</comment>
<file context>
@@ -0,0 +1,29 @@
+ sendfile on;
+ tcp_nopush on;
+ keepalive_timeout 65;
+ types_hash_max_size 4096;
+
+ include /etc/nginx/http.d/*.conf;
</file context>
…operation_log
Prevents silent deletion of audit log history when a user is deleted.
Aligns with the pattern used by reagent_order_operation_log and
consumable_order_operation_log.
The reason will be displayed to describe this comment to others. Learn more.
1 issue found across 4 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="frontend/src/components/BaseForm.tsx">
<violation number="1">
P2: Number parsing relies on `valueAsNumber`, but `Input`'s synthetic change events only carry `target.value`, so stepper-triggered updates for `type: 'number'` fields can be lost.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
The reason will be displayed to describe this comment to others. Learn more.
2 issues found across 6 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="app/core/db_compat.py">
<violation number="1" location="app/core/db_compat.py:38">
P2: Fallback logic breaks DELETE statements that have no WHERE clause on SQLite < 3.35, because `.where(None)` produces `WHERE NULL` and causes an early return before deletion.</violation>
<violation number="2" location="app/core/db_compat.py:58">
P2: `exec_delete_returning_all` has the same `.where(None)` fallback bug, causing unconditional deletes to return `[]` and skip the delete.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
The reason will be displayed to describe this comment to others. Learn more.
P2: exec_delete_returning_all has the same .where(None) fallback bug, causing unconditional deletes to return [] and skip the delete.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/core/db_compat.py, line 58:
<comment>`exec_delete_returning_all` has the same `.where(None)` fallback bug, causing unconditional deletes to return `[]` and skip the delete.</comment>
<file context>
@@ -0,0 +1,62 @@
+ rows = db.exec(delete_stmt.returning(*model_cls.__table__.columns)).all()
+ return [model_cls.model_validate(dict(row._mapping)) for row in rows]
+ # Fallback: fetch all matching rows first, then delete them.
+ existing = db.exec(select(model_cls).where(delete_stmt.whereclause)).all()
+ if not existing:
+ return []
</file context>
The reason will be displayed to describe this comment to others. Learn more.
P2: Fallback logic breaks DELETE statements that have no WHERE clause on SQLite < 3.35, because .where(None) produces WHERE NULL and causes an early return before deletion.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/core/db_compat.py, line 38:
<comment>Fallback logic breaks DELETE statements that have no WHERE clause on SQLite < 3.35, because `.where(None)` produces `WHERE NULL` and causes an early return before deletion.</comment>
<file context>
@@ -0,0 +1,62 @@
+ return None
+ return model_cls.model_validate(dict(row._mapping))
+ # Fallback: fetch the row first, then delete it.
+ existing = db.exec(select(model_cls).where(delete_stmt.whereclause)).first()
+ if existing is None:
+ return None
</file context>
The reason will be displayed to describe this comment to others. Learn more.
Access to a protected member _mapping of a client class
Accessing a protected member (a member prefixed with _) of a class from outside that class is not recommended, since the creator of that class did not intend this member to be exposed. If accesing this attribute outside of the class is absolutely needed, refactor it such that it becomes part of the public interface of the class.
The reason will be displayed to describe this comment to others. Learn more.
Access to a protected member _mapping of a client class
Accessing a protected member (a member prefixed with _) of a class from outside that class is not recommended, since the creator of that class did not intend this member to be exposed. If accesing this attribute outside of the class is absolutely needed, refactor it such that it becomes part of the public interface of the class.
The reason will be displayed to describe this comment to others. Learn more.
WARNING: Duplicate state update - setIsCapsLockOn(false) is called twice consecutively (lines 49 and 50). The second call is redundant and should be removed.
The reason will be displayed to describe this comment to others. Learn more.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="frontend/src/components/ui/PasswordInput.tsx">
<violation number="1">
P3: Duplicate `setIsCapsLockOn(false)` call — remove the extra line.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
本次 PR 将
develop分支合并到main,主要包含库存链路、操作日志、SSE、前端存储重构,以及 CommonShelf / CAS 主数据能力的完整接入。主要变更
后端
CommonShelf、chemical_name_map及相关日志模型CommonShelf与 CAS 主数据管理接口前端
影响范围
app/api/**app/models/**app/services/**frontend/src/**browser-extension/**docker/**测试
已完成:
frontend/npm run lintfrontend/npm run buildruff check app/风险点
Summary by cubic
完整接入 CommonShelf 与 CAS 主数据,重构库存到货/入库链路,并新增统一操作日志,提升可追溯性与易用性;同时完善 FTS 参数名、请求头读取、数字输入 onChange 与 SQLite RETURNING 兼容等细节,并在登录密码输入增加大写锁提示。同步引入版本化缓存失效、导入预览确认两阶段、SSE 稳定性优化、浏览器扩展升级与相关文档同步。
New Features
CommonShelf、chemical_name_map模型与接口,支持分组增删、合并、加瓶/扣减、导出与 FTS 搜索;补充纯度字段。common_shelf_operation_log.operator_id外键为 SET NULL,避免删用户误删审计历史。robots.txt);登录密码输入新增大写锁提示;同步相关 wiki 文档。webRequest权限。nginxBrotli,长效缓存/lib与/assets;更新docker/nginx配置。app/core/db_compat.py并替换所有调用点。Migration
CACHE_VERSION。部署后如需强制清理前后端缓存并让所有用户重新登录,请递增该值(留空时回退到APP_VERSION)。docker/nginx/nginx.conf与default.conf,开启 Brotli 与静态资源长缓存。Written for commit 4af4f70. Summary will update on new commits.