Skip to content

Enhance share functionality with metadata, expiry options, and updates - #114

Merged
alvin000009238 merged 15 commits into
mainfrom
dev
Apr 1, 2026
Merged

Enhance share functionality with metadata, expiry options, and updates#114
alvin000009238 merged 15 commits into
mainfrom
dev

Conversation

@alvin000009238

Copy link
Copy Markdown
Owner

No description provided.

Copilot AI review requested due to automatic review settings April 1, 2026 04:36

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the ability to update existing share links and allows users to choose between different expiry durations (2 hours or 7 days). It implements metadata tracking to associate share links with their creators, ensuring that only the original owner can perform updates. Key changes include the addition of a PUT endpoint for share updates, new service methods for metadata management in Redis, and frontend logic to track and sync the active share ID. The review feedback identifies redundant conditional checks for the Redis client and student session data in the update route, and highlights a brittle validation check for expiry options that may cause issues if configuration options are modified in the future.

Comment thread app/routes/share.py
if requester_student_no is None:
return jsonify({'error': 'Unauthorized'}), 401

rate_limit_subject = requester_student_no or request.remote_addr

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The fallback to request.remote_addr is redundant because requester_student_no is guaranteed to be truthy at this point due to the check at line 103.

Suggested change
rate_limit_subject = requester_student_no or request.remote_addr
rate_limit_subject = requester_student_no

Comment thread app/routes/share.py
Comment on lines +107 to +123
if redis_client:
try:
limited, remaining, retry_after = is_rate_limited(
redis_client,
rate_limit_subject,
max_attempts=10,
window_seconds=60,
key_prefix='share_update'
)
if limited:
resp = jsonify({
'error': f'更新分享過於頻繁,請在 {retry_after} 秒後再試',
})
resp.headers['Retry-After'] = str(retry_after)
return resp, 429
except Exception as exc:
logger.error(f'Share update rate limiter error: {exc}', exc_info=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The if redis_client: check is redundant because redis_client is already verified at line 99.

        try:
            limited, remaining, retry_after = is_rate_limited(
                redis_client,
                rate_limit_subject,
                max_attempts=10,
                window_seconds=60,
                key_prefix='share_update'
            )
            if limited:
                resp = jsonify({
                    'error': f'更新分享過於頻繁,請在 {retry_after} 秒後再試',
                })
                resp.headers['Retry-After'] = str(retry_after)
                return resp, 429
        except Exception as exc:
            logger.error(f'Share update rate limiter error: {exc}', exc_info=True)

Comment thread app/routes/share.py
Comment on lines +141 to +142
if share_ttl not in SHARE_EXPIRY_OPTIONS.values():
return jsonify({'error': 'Share metadata state conflict'}), 409

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This check is brittle. If the available SHARE_EXPIRY_OPTIONS change in the future, existing valid share links might become impossible to update even if they haven't expired. Since this is an update to an existing link, we should respect the TTL already stored in the metadata rather than enforcing it against the current UI options.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the share-link feature by adding server-side share metadata, configurable expiry options, and an authenticated “update existing share” flow (with frontend auto-update after sync).

Changes:

  • Backend: add share metadata storage, expiry selection (2h / 7d), and a new authenticated PUT /api/share/<id> endpoint with TTL refresh + ownership checks.
  • Frontend: allow selecting share expiry on creation, persist an activeShareId, and auto-update the active share after syncing grades.
  • Tests: extend share service tests and add a new backend route test suite covering create/update/expiry/auth/rate-limit/redis-unavailable cases.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/backend/test_share.py Updates share-id generation test and validates stripping of share_expiry.
tests/backend/test_share_routes.py Adds route-level tests for create/update flows, TTL behavior, expiry validation, authz, and error handling.
frontend/sync.js Calls updateActiveShare() after a successful sync to keep shares current.
frontend/share.js Adds expiry selector UI, stores activeShareId, and implements updateActiveShare() via PUT /api/share/<id>.
app/services/share_service.py Introduces fixed-length share IDs, share metadata read/write, and metadata TTL refresh helpers.
app/routes/share.py Adds expiry option handling, requires login for share creation, adds update endpoint with ownership + TTL refresh, and returns 503 when Redis is unavailable.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread frontend/share.js Outdated
body: JSON.stringify(gradesData)
});

if (!res.ok && (res.status === 404 || res.status === 403)) {

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateActiveShare only clears activeShareId on 404/403. If localStorage contains a malformed/old ID, the backend will return 400 (Invalid ID format) and the client will keep retrying on every sync, causing repeated failing requests/noise. Consider also removing activeShareId on 400 responses (and potentially other non-retryable statuses).

Suggested change
if (!res.ok && (res.status === 404 || res.status === 403)) {
if (!res.ok && (res.status === 400 || res.status === 403 || res.status === 404)) {

Copilot uses AI. Check for mistakes.
Comment thread app/routes/share.py
Comment on lines +148 to +150
except Exception as exc:
logger.error(f'Error updating share: {exc}', exc_info=True)
return jsonify({'error': str(exc)}), 500

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception handler returns str(exc) to the client. This can leak internal details (e.g., backend errors, dependency messages) and makes it harder to keep error responses consistent. Prefer returning a generic 500 message (optionally with a request/error id) while keeping full details in logs.

Copilot uses AI. Check for mistakes.
@alvin000009238
alvin000009238 merged commit a6178e0 into main Apr 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants