mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-22 06:10:24 -05:00
8c2714bec3
- /api/activity: return 400 with clear message when start_date/end_date are not valid ISO 8601; avoid silent pass on parse errors - Web route /activity: catch ValueError, log and skip filter instead of 500 - Add tests for invalid date formats on API and web routes
156 lines
5.0 KiB
Python
156 lines
5.0 KiB
Python
"""
|
|
Activity Feed routes
|
|
"""
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
from flask import Blueprint, current_app, jsonify, render_template, request
|
|
from flask_babel import gettext as _
|
|
from flask_login import current_user, login_required
|
|
from sqlalchemy import and_
|
|
|
|
from app import db
|
|
from app.models import Activity
|
|
from app.utils.module_helpers import module_enabled
|
|
|
|
activity_feed_bp = Blueprint("activity_feed", __name__)
|
|
|
|
|
|
@activity_feed_bp.route("/activity")
|
|
@login_required
|
|
@module_enabled("activity_feed")
|
|
def activity_feed():
|
|
"""Main activity feed page"""
|
|
# Get query parameters
|
|
limit = request.args.get("limit", 50, type=int)
|
|
page = request.args.get("page", 1, type=int)
|
|
user_id = request.args.get("user_id", type=int)
|
|
entity_type = request.args.get("entity_type", "").strip()
|
|
action = request.args.get("action", "").strip()
|
|
|
|
# Build query
|
|
query = Activity.query
|
|
|
|
# Apply filters
|
|
if user_id:
|
|
query = query.filter_by(user_id=user_id)
|
|
|
|
if entity_type:
|
|
query = query.filter_by(entity_type=entity_type)
|
|
|
|
if action:
|
|
query = query.filter_by(action=action)
|
|
|
|
# Date filters
|
|
start_date = request.args.get("start_date", "").strip()
|
|
end_date = request.args.get("end_date", "").strip()
|
|
|
|
if start_date:
|
|
try:
|
|
start_dt = datetime.fromisoformat(start_date.replace("Z", "+00:00"))
|
|
query = query.filter(Activity.created_at >= start_dt)
|
|
except ValueError:
|
|
current_app.logger.debug("Invalid activity feed start_date param: %r", start_date)
|
|
|
|
if end_date:
|
|
try:
|
|
end_dt = datetime.fromisoformat(end_date.replace("Z", "+00:00"))
|
|
query = query.filter(Activity.created_at <= end_dt)
|
|
except ValueError:
|
|
current_app.logger.debug("Invalid activity feed end_date param: %r", end_date)
|
|
|
|
# Paginate
|
|
per_page = min(limit, 100) # Max 100 per page
|
|
paginated = query.order_by(Activity.created_at.desc()).paginate(page=page, per_page=per_page, error_out=False)
|
|
|
|
# Get filter options
|
|
entity_types = db.session.query(Activity.entity_type).distinct().all() if hasattr(db, "session") else []
|
|
actions = db.session.query(Activity.action).distinct().all() if hasattr(db, "session") else []
|
|
|
|
return render_template(
|
|
"activity/feed.html",
|
|
activities=paginated.items,
|
|
pagination=paginated,
|
|
entity_types=[e[0] for e in entity_types],
|
|
actions=[a[0] for a in actions],
|
|
filters={
|
|
"user_id": user_id,
|
|
"entity_type": entity_type,
|
|
"action": action,
|
|
"start_date": start_date,
|
|
"end_date": end_date,
|
|
},
|
|
)
|
|
|
|
|
|
@activity_feed_bp.route("/api/activity")
|
|
@login_required
|
|
@module_enabled("activity_feed")
|
|
def api_activity_feed():
|
|
"""API endpoint for activity feed"""
|
|
# Get query parameters
|
|
limit = request.args.get("limit", 50, type=int)
|
|
page = request.args.get("page", 1, type=int)
|
|
user_id = request.args.get("user_id", type=int)
|
|
entity_type = request.args.get("entity_type", "").strip()
|
|
action = request.args.get("action", "").strip()
|
|
start_date = request.args.get("start_date", "").strip()
|
|
end_date = request.args.get("end_date", "").strip()
|
|
|
|
# Build query
|
|
query = Activity.query
|
|
|
|
# Apply filters
|
|
if user_id:
|
|
query = query.filter_by(user_id=user_id)
|
|
|
|
if entity_type:
|
|
query = query.filter_by(entity_type=entity_type)
|
|
|
|
if action:
|
|
query = query.filter_by(action=action)
|
|
|
|
if start_date:
|
|
try:
|
|
start_dt = datetime.fromisoformat(start_date.replace("Z", "+00:00"))
|
|
query = query.filter(Activity.created_at >= start_dt)
|
|
except ValueError:
|
|
return (
|
|
jsonify({
|
|
"error": "Invalid parameter",
|
|
"message": "Invalid start_date or end_date format; use ISO 8601 (e.g. 2024-01-15 or 2024-01-15T00:00:00Z).",
|
|
}),
|
|
400,
|
|
)
|
|
|
|
if end_date:
|
|
try:
|
|
end_dt = datetime.fromisoformat(end_date.replace("Z", "+00:00"))
|
|
query = query.filter(Activity.created_at <= end_dt)
|
|
except ValueError:
|
|
return (
|
|
jsonify({
|
|
"error": "Invalid parameter",
|
|
"message": "Invalid start_date or end_date format; use ISO 8601 (e.g. 2024-01-15 or 2024-01-15T00:00:00Z).",
|
|
}),
|
|
400,
|
|
)
|
|
|
|
# Paginate
|
|
per_page = min(limit, 100)
|
|
paginated = query.order_by(Activity.created_at.desc()).paginate(page=page, per_page=per_page, error_out=False)
|
|
|
|
return jsonify(
|
|
{
|
|
"activities": [a.to_dict() for a in paginated.items],
|
|
"pagination": {
|
|
"page": paginated.page,
|
|
"per_page": paginated.per_page,
|
|
"total": paginated.total,
|
|
"pages": paginated.pages,
|
|
"has_next": paginated.has_next,
|
|
"has_prev": paginated.has_prev,
|
|
},
|
|
}
|
|
)
|