flask-vim-docs/app/routes.py
2025-04-14 22:25:26 +02:00

268 lines
No EOL
9.5 KiB
Python

from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, send_file
from flask_login import current_user, login_required
from app import db
from app.models.document import Document, Category, Tag
from app.models.user import User
import json
from datetime import datetime
import io
from sqlalchemy import or_
main = Blueprint('main', __name__)
@main.route('/')
@login_required
def index():
"""Home page showing categories and recent documents"""
root_categories = Category.query.filter_by(
user_id=current_user.id,
parent_id=None
).all()
recent_docs = Document.query.filter_by(
user_id=current_user.id
).order_by(Document.updated_date.desc()).limit(5).all()
return render_template('index.html', categories=root_categories, recent_docs=recent_docs)
@main.route('/category/<int:category_id>')
@login_required
def view_category(category_id):
"""View a specific category and its documents"""
category = Category.query.filter_by(id=category_id, user_id=current_user.id).first_or_404()
return render_template('category.html', category=category)
@main.route('/document/<int:doc_id>', methods=['GET'])
@login_required
def view_document(doc_id):
"""View a document in read mode"""
document = Document.query.filter_by(id=doc_id, user_id=current_user.id).first_or_404()
return render_template('document_view.html', document=document)
@main.route('/document/<int:doc_id>/edit', methods=['GET'])
@login_required
def edit_document(doc_id):
"""Edit a document with the Vim editor"""
document = Document.query.filter_by(id=doc_id, user_id=current_user.id).first_or_404()
categories = Category.query.filter_by(user_id=current_user.id).all()
tags = Tag.query.filter_by(user_id=current_user.id).all()
return render_template('document_edit.html', document=document, categories=categories, tags=tags)
@main.route('/document/new', methods=['GET'])
@login_required
def new_document():
"""Create a new document with the Vim editor"""
categories = Category.query.filter_by(user_id=current_user.id).all()
tags = Tag.query.filter_by(user_id=current_user.id).all()
category_id = request.args.get('category')
document = None
if category_id:
category = Category.query.filter_by(id=category_id, user_id=current_user.id).first()
if category:
document = Document(
title="Untitled Document",
content="",
category_id=category_id,
user_id=current_user.id
)
return render_template('document_edit.html', document=document, categories=categories, tags=tags, preselected_category_id=category_id)
@main.route('/api/document', methods=['POST'])
@login_required
def save_document():
"""Save a document (new or existing)"""
data = request.json
# Get root category as default
root_category = Category.query.filter_by(user_id=current_user.id, is_root=True).first()
default_category_id = root_category.id if root_category else None
if 'id' in data and data['id']:
# Update existing document - verify ownership
document = Document.query.filter_by(id=data['id'], user_id=current_user.id).first_or_404()
document.title = data['title']
document.content = data['content']
document.category_id = data['category_id'] if data['category_id'] else default_category_id
else:
# Create new document
document = Document(
title=data['title'],
content=data['content'],
category_id=data['category_id'] if data['category_id'] else default_category_id,
user_id=current_user.id
)
db.session.add(document)
# Handle tags
if 'tags' in data:
document.tags = []
for tag_name in data['tags']:
tag = Tag.query.filter_by(name=tag_name, user_id=current_user.id).first()
if not tag:
tag = Tag(name=tag_name, user_id=current_user.id)
db.session.add(tag)
document.tags.append(tag)
db.session.commit()
return jsonify(document.to_dict())
@main.route('/api/document/<int:doc_id>', methods=['DELETE'])
@login_required
def delete_document(doc_id):
"""Delete a document"""
document = Document.query.filter_by(id=doc_id, user_id=current_user.id).first_or_404()
db.session.delete(document)
db.session.commit()
return jsonify({'success': True})
@main.route('/api/category', methods=['POST'])
@login_required
def save_category():
"""Save a category (new or existing)"""
data = request.json
if 'id' in data and data['id']:
# Update existing category - verify ownership
category = Category.query.filter_by(id=data['id'], user_id=current_user.id).first_or_404()
category.name = data['name']
category.icon = data['icon']
category.description = data.get('description', '')
# Only change parent if it belongs to the same user
if data.get('parent_id'):
parent = Category.query.filter_by(id=data['parent_id'], user_id=current_user.id).first()
if parent:
category.parent_id = parent.id
else:
# Create new category
category = Category(
name=data['name'],
icon=data['icon'],
description=data.get('description', ''),
parent_id=data['parent_id'] if data.get('parent_id') else None,
user_id=current_user.id
)
db.session.add(category)
db.session.commit()
return jsonify(category.to_dict())
@main.route('/api/category/<int:category_id>', methods=['DELETE'])
@login_required
def delete_category(category_id):
"""Delete a category and optionally reassign documents"""
category = Category.query.filter_by(id=category_id, user_id=current_user.id).first_or_404()
# Can't delete root category
if category.is_root:
return jsonify({'error': 'Cannot delete root category'}), 400
# Get target category for documents if specified
new_category_id = request.args.get('new_category_id')
if new_category_id:
new_category = Category.query.filter_by(id=new_category_id, user_id=current_user.id).first()
if new_category:
# Reassign documents
for doc in category.documents:
doc.category_id = new_category.id
else:
# Move documents to no category
for doc in category.documents:
doc.category_id = None
# Also handle child categories
for child in category.children:
if new_category_id:
child.parent_id = new_category_id
else:
child.parent_id = None
db.session.delete(category)
db.session.commit()
return jsonify({'success': True})
@main.route('/api/search', methods=['GET'])
@login_required
def search_documents():
"""Search for documents by title, content, or tags"""
query = request.args.get('q', '')
if not query or len(query) < 2:
return jsonify({'results': []})
# Search in title, content, and tags for current user's documents only
docs = Document.query.filter(
Document.user_id == current_user.id,
or_(
Document.title.ilike(f'%{query}%'),
Document.content.ilike(f'%{query}%'),
Document.tags.any(Tag.name.ilike(f'%{query}%'))
)
).limit(10).all()
results = []
for doc in docs:
# Find match in content
match = None
if query.lower() in doc.content.lower():
# Find the sentence containing the match
content_lower = doc.content.lower()
query_pos = content_lower.find(query.lower())
# Get a snippet around the match
start = max(0, content_lower.rfind('.', 0, query_pos) + 1)
end = content_lower.find('.', query_pos)
if end == -1:
end = min(len(doc.content), query_pos + 200)
match = doc.content[start:end].strip()
# Get category name
category_name = doc.category.name if doc.category else None
results.append({
'id': doc.id,
'title': doc.title,
'category': category_name,
'tags': [tag.name for tag in doc.tags],
'match': match
})
return jsonify({'results': results})
@main.route('/api/tags', methods=['GET'])
@login_required
def get_tags():
"""Get all tags for the current user"""
tags = Tag.query.filter_by(user_id=current_user.id).all()
return jsonify({'tags': [tag.to_dict() for tag in tags]})
@main.route('/document/<int:doc_id>/export', methods=['GET'])
@login_required
def export_document(doc_id):
"""Export a document as a markdown file"""
document = Document.query.filter_by(id=doc_id, user_id=current_user.id).first_or_404()
# Create a file-like object in memory
file_data = io.BytesIO(document.content.encode('utf-8'))
file_data.seek(0)
return send_file(
file_data,
mimetype='text/markdown',
as_attachment=True,
download_name=f"{document.title.replace(' ', '_')}.md"
)
@main.route('/api/categories', methods=['GET'])
@login_required
def get_categories():
"""Get all root categories with their children for the current user"""
root_categories = Category.query.filter_by(
user_id=current_user.id,
parent_id=None
).all()
return jsonify([category.to_dict() for category in root_categories])