2nd
This commit is contained in:
@@ -49,12 +49,13 @@ def get_todos():
|
||||
query = query.join(TodoItemFollower).filter(
|
||||
TodoItemFollower.ad_account == identity
|
||||
)
|
||||
else: # all
|
||||
else: # all - show todos user can view (public + private with access)
|
||||
query = query.filter(
|
||||
or_(
|
||||
TodoItem.creator_ad == identity,
|
||||
TodoItem.responsible_users.any(TodoItemResponsible.ad_account == identity),
|
||||
TodoItem.followers.any(TodoItemFollower.ad_account == identity)
|
||||
TodoItem.is_public == True, # All public todos
|
||||
TodoItem.creator_ad == identity, # Created by user
|
||||
TodoItem.responsible_users.any(TodoItemResponsible.ad_account == identity), # User is responsible
|
||||
TodoItem.followers.any(TodoItemFollower.ad_account == identity) # User is follower
|
||||
)
|
||||
)
|
||||
|
||||
@@ -157,7 +158,9 @@ def create_todo():
|
||||
creator_ad=identity,
|
||||
creator_display_name=claims.get('display_name', identity),
|
||||
creator_email=claims.get('email', ''),
|
||||
starred=data.get('starred', False)
|
||||
starred=data.get('starred', False),
|
||||
is_public=data.get('is_public', False),
|
||||
tags=data.get('tags', [])
|
||||
)
|
||||
db.session.add(todo)
|
||||
|
||||
@@ -262,6 +265,14 @@ def update_todo(todo_id):
|
||||
changes['starred'] = {'old': todo.starred, 'new': data['starred']}
|
||||
todo.starred = data['starred']
|
||||
|
||||
if 'is_public' in data:
|
||||
changes['is_public'] = {'old': todo.is_public, 'new': data['is_public']}
|
||||
todo.is_public = data['is_public']
|
||||
|
||||
if 'tags' in data:
|
||||
changes['tags'] = {'old': todo.tags, 'new': data['tags']}
|
||||
todo.tags = data['tags']
|
||||
|
||||
# Update responsible users
|
||||
if 'responsible_users' in data:
|
||||
# Remove existing
|
||||
@@ -706,4 +717,228 @@ def star_todo(todo_id):
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
logger.error(f"Star todo error: {str(e)}")
|
||||
return jsonify({'error': 'Failed to star todo'}), 500
|
||||
return jsonify({'error': 'Failed to star todo'}), 500
|
||||
|
||||
@todos_bp.route('/public', methods=['GET'])
|
||||
@jwt_required()
|
||||
def get_public_todos():
|
||||
"""Get all public todos"""
|
||||
try:
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 20, type=int)
|
||||
|
||||
# Filters for public todos
|
||||
status = request.args.get('status')
|
||||
priority = request.args.get('priority')
|
||||
search = request.args.get('search')
|
||||
tags = request.args.getlist('tags')
|
||||
|
||||
# Query only public todos
|
||||
query = TodoItem.query.filter(TodoItem.is_public == True).options(
|
||||
joinedload(TodoItem.responsible_users),
|
||||
joinedload(TodoItem.followers)
|
||||
)
|
||||
|
||||
# Apply filters
|
||||
if status:
|
||||
query = query.filter(TodoItem.status == status)
|
||||
if priority:
|
||||
query = query.filter(TodoItem.priority == priority)
|
||||
if search:
|
||||
query = query.filter(
|
||||
or_(
|
||||
TodoItem.title.contains(search),
|
||||
TodoItem.description.contains(search)
|
||||
)
|
||||
)
|
||||
if tags:
|
||||
for tag in tags:
|
||||
query = query.filter(TodoItem.tags.contains(tag))
|
||||
|
||||
# Order by created_at desc
|
||||
query = query.order_by(TodoItem.created_at.desc())
|
||||
|
||||
# Paginate
|
||||
paginated = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
|
||||
return jsonify({
|
||||
'todos': [todo.to_dict() for todo in paginated.items],
|
||||
'total': paginated.total,
|
||||
'pages': paginated.pages,
|
||||
'current_page': page
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Get public todos error: {str(e)}")
|
||||
return jsonify({'error': 'Failed to fetch public todos'}), 500
|
||||
|
||||
@todos_bp.route('/following', methods=['GET'])
|
||||
@jwt_required()
|
||||
def get_following_todos():
|
||||
"""Get todos that user is following"""
|
||||
try:
|
||||
identity = get_jwt_identity()
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 20, type=int)
|
||||
|
||||
# Query todos where user is a follower
|
||||
query = TodoItem.query.join(TodoItemFollower).filter(
|
||||
TodoItemFollower.ad_account == identity
|
||||
).options(
|
||||
joinedload(TodoItem.responsible_users),
|
||||
joinedload(TodoItem.followers)
|
||||
)
|
||||
|
||||
# Order by created_at desc
|
||||
query = query.order_by(TodoItem.created_at.desc())
|
||||
|
||||
# Paginate
|
||||
paginated = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
|
||||
return jsonify({
|
||||
'todos': [todo.to_dict() for todo in paginated.items],
|
||||
'total': paginated.total,
|
||||
'pages': paginated.pages,
|
||||
'current_page': page
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Get following todos error: {str(e)}")
|
||||
return jsonify({'error': 'Failed to fetch following todos'}), 500
|
||||
|
||||
@todos_bp.route('/<todo_id>/visibility', methods=['PATCH'])
|
||||
@jwt_required()
|
||||
def update_todo_visibility(todo_id):
|
||||
"""Toggle todo visibility (public/private)"""
|
||||
try:
|
||||
identity = get_jwt_identity()
|
||||
|
||||
# Get todo
|
||||
todo = TodoItem.query.get(todo_id)
|
||||
if not todo:
|
||||
return jsonify({'error': 'Todo not found'}), 404
|
||||
|
||||
# Only creator can change visibility
|
||||
if todo.creator_ad != identity:
|
||||
return jsonify({'error': 'Only creator can change visibility'}), 403
|
||||
|
||||
# Toggle visibility
|
||||
data = request.get_json()
|
||||
is_public = data.get('is_public', not todo.is_public)
|
||||
todo.is_public = is_public
|
||||
|
||||
# Log audit
|
||||
audit = TodoAuditLog(
|
||||
actor_ad=identity,
|
||||
todo_id=todo_id,
|
||||
action='UPDATE',
|
||||
detail={
|
||||
'field': 'is_public',
|
||||
'old_value': not is_public,
|
||||
'new_value': is_public
|
||||
}
|
||||
)
|
||||
db.session.add(audit)
|
||||
db.session.commit()
|
||||
|
||||
logger.info(f"Todo {todo_id} visibility changed to {'public' if is_public else 'private'} by {identity}")
|
||||
|
||||
return jsonify({
|
||||
'message': f'Todo is now {"public" if is_public else "private"}',
|
||||
'is_public': todo.is_public
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
logger.error(f"Update visibility error: {str(e)}")
|
||||
return jsonify({'error': 'Failed to update visibility'}), 500
|
||||
|
||||
@todos_bp.route('/<todo_id>/follow', methods=['POST'])
|
||||
@jwt_required()
|
||||
def follow_todo(todo_id):
|
||||
"""Follow a public todo"""
|
||||
try:
|
||||
identity = get_jwt_identity()
|
||||
|
||||
# Get todo
|
||||
todo = TodoItem.query.get(todo_id)
|
||||
if not todo:
|
||||
return jsonify({'error': 'Todo not found'}), 404
|
||||
|
||||
# Check if todo is public or user has permission
|
||||
if not todo.is_public and not todo.can_edit(identity):
|
||||
return jsonify({'error': 'Cannot follow private todo'}), 403
|
||||
|
||||
# Check if already following
|
||||
existing = TodoItemFollower.query.filter_by(
|
||||
todo_id=todo_id,
|
||||
ad_account=identity
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
return jsonify({'message': 'Already following this todo'}), 200
|
||||
|
||||
# Add follower
|
||||
follower = TodoItemFollower(
|
||||
todo_id=todo_id,
|
||||
ad_account=identity,
|
||||
added_by=identity
|
||||
)
|
||||
db.session.add(follower)
|
||||
|
||||
# Log audit
|
||||
audit = TodoAuditLog(
|
||||
actor_ad=identity,
|
||||
todo_id=todo_id,
|
||||
action='FOLLOW',
|
||||
detail={'follower': identity}
|
||||
)
|
||||
db.session.add(audit)
|
||||
db.session.commit()
|
||||
|
||||
logger.info(f"User {identity} followed todo {todo_id}")
|
||||
|
||||
return jsonify({'message': 'Successfully followed todo'}), 200
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
logger.error(f"Follow todo error: {str(e)}")
|
||||
return jsonify({'error': 'Failed to follow todo'}), 500
|
||||
|
||||
@todos_bp.route('/<todo_id>/follow', methods=['DELETE'])
|
||||
@jwt_required()
|
||||
def unfollow_todo(todo_id):
|
||||
"""Unfollow a todo"""
|
||||
try:
|
||||
identity = get_jwt_identity()
|
||||
|
||||
# Get follower record
|
||||
follower = TodoItemFollower.query.filter_by(
|
||||
todo_id=todo_id,
|
||||
ad_account=identity
|
||||
).first()
|
||||
|
||||
if not follower:
|
||||
return jsonify({'message': 'Not following this todo'}), 200
|
||||
|
||||
# Remove follower
|
||||
db.session.delete(follower)
|
||||
|
||||
# Log audit
|
||||
audit = TodoAuditLog(
|
||||
actor_ad=identity,
|
||||
todo_id=todo_id,
|
||||
action='UNFOLLOW',
|
||||
detail={'follower': identity}
|
||||
)
|
||||
db.session.add(audit)
|
||||
db.session.commit()
|
||||
|
||||
logger.info(f"User {identity} unfollowed todo {todo_id}")
|
||||
|
||||
return jsonify({'message': 'Successfully unfollowed todo'}), 200
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
logger.error(f"Unfollow todo error: {str(e)}")
|
||||
return jsonify({'error': 'Failed to unfollow todo'}), 500
|
Reference in New Issue
Block a user