Move all Notes related parts into own modules.

This commit is contained in:
2025-07-06 23:22:20 +02:00
parent 60020e32f6
commit e12681cb08
13 changed files with 1309 additions and 1216 deletions

388
routes/notes_api.py Normal file
View File

@@ -0,0 +1,388 @@
# Standard library imports
from datetime import datetime, timezone
# Third-party imports
from flask import Blueprint, abort, g, jsonify, request
from sqlalchemy import and_, or_
# Local application imports
from models import Note, NoteFolder, NoteLink, NoteVisibility, db
from routes.auth import company_required, login_required
# Create blueprint
notes_api_bp = Blueprint('notes_api', __name__, url_prefix='/api/notes')
@notes_api_bp.route('/folder-details')
@login_required
@company_required
def api_folder_details():
"""Get folder details including note count"""
folder_path = request.args.get('folder', '')
# Get note count for this folder
note_count = Note.query.filter_by(
company_id=g.user.company_id,
folder=folder_path,
is_archived=False
).count()
# Check if folder exists in NoteFolder table
folder = NoteFolder.query.filter_by(
company_id=g.user.company_id,
path=folder_path
).first()
return jsonify({
'success': True,
'folder': {
'path': folder_path,
'name': folder_path.split('/')[-1] if folder_path else 'Root',
'exists': folder is not None,
'note_count': note_count,
'created_at': folder.created_at.isoformat() if folder else None
}
})
@notes_api_bp.route('/folders', methods=['POST'])
@login_required
@company_required
def api_create_folder():
"""Create a new folder"""
data = request.get_json()
if not data:
return jsonify({'success': False, 'message': 'No data provided'}), 400
folder_name = data.get('name', '').strip()
parent_path = data.get('parent', '').strip()
if not folder_name:
return jsonify({'success': False, 'message': 'Folder name is required'}), 400
# Validate folder name
if '/' in folder_name:
return jsonify({'success': False, 'message': 'Folder name cannot contain /'}), 400
# Build full path
if parent_path:
full_path = f"{parent_path}/{folder_name}"
else:
full_path = folder_name
# Check if folder already exists
existing = NoteFolder.query.filter_by(
company_id=g.user.company_id,
path=full_path
).first()
if existing:
return jsonify({'success': False, 'message': 'Folder already exists'}), 400
# Create folder
folder = NoteFolder(
path=full_path,
company_id=g.user.company_id,
created_by_id=g.user.id
)
db.session.add(folder)
db.session.commit()
return jsonify({
'success': True,
'message': 'Folder created successfully',
'folder': {
'path': folder.path,
'name': folder_name
}
})
@notes_api_bp.route('/folders', methods=['PUT'])
@login_required
@company_required
def api_rename_folder():
"""Rename a folder"""
data = request.get_json()
if not data:
return jsonify({'success': False, 'message': 'No data provided'}), 400
old_path = data.get('old_path', '').strip()
new_name = data.get('new_name', '').strip()
if not old_path or not new_name:
return jsonify({'success': False, 'message': 'Both old path and new name are required'}), 400
# Validate new name
if '/' in new_name:
return jsonify({'success': False, 'message': 'Folder name cannot contain /'}), 400
# Find the folder
folder = NoteFolder.query.filter_by(
company_id=g.user.company_id,
path=old_path
).first()
if not folder:
return jsonify({'success': False, 'message': 'Folder not found'}), 404
# Build new path
path_parts = old_path.split('/')
path_parts[-1] = new_name
new_path = '/'.join(path_parts)
# Check if new path already exists
existing = NoteFolder.query.filter_by(
company_id=g.user.company_id,
path=new_path
).first()
if existing:
return jsonify({'success': False, 'message': 'A folder with this name already exists'}), 400
# Update folder path
folder.path = new_path
# Update all notes in this folder
Note.query.filter_by(
company_id=g.user.company_id,
folder=old_path
).update({Note.folder: new_path})
# Update all subfolders
subfolders = NoteFolder.query.filter(
NoteFolder.company_id == g.user.company_id,
NoteFolder.path.like(f"{old_path}/%")
).all()
for subfolder in subfolders:
subfolder.path = subfolder.path.replace(old_path, new_path, 1)
# Update all notes in subfolders
notes_in_subfolders = Note.query.filter(
Note.company_id == g.user.company_id,
Note.folder.like(f"{old_path}/%")
).all()
for note in notes_in_subfolders:
note.folder = note.folder.replace(old_path, new_path, 1)
db.session.commit()
return jsonify({
'success': True,
'message': 'Folder renamed successfully',
'folder': {
'old_path': old_path,
'new_path': new_path
}
})
@notes_api_bp.route('/folders', methods=['DELETE'])
@login_required
@company_required
def api_delete_folder():
"""Delete a folder"""
folder_path = request.args.get('path', '').strip()
if not folder_path:
return jsonify({'success': False, 'message': 'Folder path is required'}), 400
# Check if folder has notes
note_count = Note.query.filter_by(
company_id=g.user.company_id,
folder=folder_path,
is_archived=False
).count()
if note_count > 0:
return jsonify({
'success': False,
'message': f'Cannot delete folder with {note_count} notes. Please move or delete the notes first.'
}), 400
# Find and delete the folder
folder = NoteFolder.query.filter_by(
company_id=g.user.company_id,
path=folder_path
).first()
if folder:
db.session.delete(folder)
db.session.commit()
return jsonify({
'success': True,
'message': 'Folder deleted successfully'
})
@notes_api_bp.route('/<slug>/folder', methods=['PUT'])
@login_required
@company_required
def update_note_folder(slug):
"""Update a note's folder via drag and drop"""
note = Note.query.filter_by(slug=slug, company_id=g.user.company_id).first_or_404()
# Check permissions
if not note.can_user_edit(g.user):
return jsonify({'success': False, 'message': 'Permission denied'}), 403
data = request.get_json()
new_folder = data.get('folder', '').strip()
# Update note folder
note.folder = new_folder if new_folder else None
note.updated_at = datetime.now(timezone.utc)
db.session.commit()
return jsonify({
'success': True,
'message': 'Note moved successfully'
})
@notes_api_bp.route('/<int:note_id>/tags', methods=['POST'])
@login_required
@company_required
def add_tags_to_note(note_id):
"""Add tags to a note"""
note = Note.query.filter_by(id=note_id, company_id=g.user.company_id).first()
if not note:
return jsonify({'success': False, 'message': 'Note not found'}), 404
# Check permissions
if not note.can_user_edit(g.user):
return jsonify({'success': False, 'message': 'Permission denied'}), 403
data = request.get_json()
new_tags = data.get('tags', '').strip()
if not new_tags:
return jsonify({'success': False, 'message': 'No tags provided'}), 400
# Merge with existing tags
existing_tags = note.get_tags_list()
new_tag_list = [tag.strip() for tag in new_tags.split(',') if tag.strip()]
# Combine and deduplicate
all_tags = list(set(existing_tags + new_tag_list))
# Update note
note.tags = ', '.join(sorted(all_tags))
note.updated_at = datetime.now(timezone.utc)
db.session.commit()
return jsonify({
'success': True,
'message': 'Tags added successfully',
'tags': all_tags
})
@notes_api_bp.route('/<int:note_id>/link', methods=['POST'])
@login_required
@company_required
def link_notes(note_id):
"""Create a link between two notes"""
source_note = Note.query.filter_by(id=note_id, company_id=g.user.company_id).first()
if not source_note:
return jsonify({'success': False, 'message': 'Source note not found'}), 404
# Check permissions
if not source_note.can_user_edit(g.user):
return jsonify({'success': False, 'message': 'Permission denied'}), 403
data = request.get_json()
target_note_id = data.get('target_note_id')
link_type = data.get('link_type', 'related')
if not target_note_id:
return jsonify({'success': False, 'message': 'Target note ID is required'}), 400
# Get target note
target_note = Note.query.filter_by(id=target_note_id, company_id=g.user.company_id).first()
if not target_note:
return jsonify({'success': False, 'message': 'Target note not found'}), 404
# Check if user can view target note
if not target_note.can_user_view(g.user):
return jsonify({'success': False, 'message': 'You cannot link to a note you cannot view'}), 403
# Check if link already exists
existing_link = NoteLink.query.filter_by(
source_note_id=source_note.id,
target_note_id=target_note.id
).first()
if existing_link:
return jsonify({'success': False, 'message': 'Link already exists'}), 400
# Create link
link = NoteLink(
source_note_id=source_note.id,
target_note_id=target_note.id,
link_type=link_type,
created_by_id=g.user.id
)
db.session.add(link)
db.session.commit()
return jsonify({
'success': True,
'message': 'Notes linked successfully'
})
@notes_api_bp.route('/<int:note_id>/link', methods=['DELETE'])
@login_required
@company_required
def unlink_notes(note_id):
"""Remove a link between two notes"""
source_note = Note.query.filter_by(id=note_id, company_id=g.user.company_id).first()
if not source_note:
return jsonify({'success': False, 'message': 'Source note not found'}), 404
# Check permissions
if not source_note.can_user_edit(g.user):
return jsonify({'success': False, 'message': 'Permission denied'}), 403
data = request.get_json()
target_note_id = data.get('target_note_id')
if not target_note_id:
return jsonify({'success': False, 'message': 'Target note ID is required'}), 400
# Find and delete the link (check both directions)
link = NoteLink.query.filter(
or_(
and_(
NoteLink.source_note_id == source_note.id,
NoteLink.target_note_id == target_note_id
),
and_(
NoteLink.source_note_id == target_note_id,
NoteLink.target_note_id == source_note.id
)
)
).first()
if not link:
return jsonify({'success': False, 'message': 'Link not found'}), 404
db.session.delete(link)
db.session.commit()
return jsonify({
'success': True,
'message': 'Link removed successfully'
})