Initial commit: Shop Database Flask Application
Flask backend with Vue 3 frontend for shop floor machine management. Includes database schema export for MySQL shopdb_flask database. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
203
shopdb/core/api/search.py
Normal file
203
shopdb/core/api/search.py
Normal file
@@ -0,0 +1,203 @@
|
||||
"""Global search API endpoint."""
|
||||
|
||||
from flask import Blueprint, request
|
||||
from flask_jwt_extended import jwt_required
|
||||
|
||||
from shopdb.extensions import db
|
||||
from shopdb.core.models import (
|
||||
Machine, Application, KnowledgeBase
|
||||
)
|
||||
from shopdb.utils.responses import success_response
|
||||
|
||||
search_bp = Blueprint('search', __name__)
|
||||
|
||||
|
||||
@search_bp.route('', methods=['GET'])
|
||||
@jwt_required(optional=True)
|
||||
def global_search():
|
||||
"""
|
||||
Global search across multiple entity types.
|
||||
|
||||
Returns combined results from:
|
||||
- Machines (equipment and PCs)
|
||||
- Applications
|
||||
- Knowledge Base articles
|
||||
- Printers (if available)
|
||||
|
||||
Results are sorted by relevance score.
|
||||
"""
|
||||
query = request.args.get('q', '').strip()
|
||||
|
||||
if not query or len(query) < 2:
|
||||
return success_response({
|
||||
'results': [],
|
||||
'query': query,
|
||||
'message': 'Search query must be at least 2 characters'
|
||||
})
|
||||
|
||||
if len(query) > 200:
|
||||
return success_response({
|
||||
'results': [],
|
||||
'query': query[:200],
|
||||
'message': 'Search query too long'
|
||||
})
|
||||
|
||||
results = []
|
||||
search_term = f'%{query}%'
|
||||
|
||||
# Search Machines (Equipment and PCs)
|
||||
machines = Machine.query.filter(
|
||||
Machine.isactive == True,
|
||||
db.or_(
|
||||
Machine.machinenumber.ilike(search_term),
|
||||
Machine.alias.ilike(search_term),
|
||||
Machine.hostname.ilike(search_term),
|
||||
Machine.serialnumber.ilike(search_term),
|
||||
Machine.notes.ilike(search_term)
|
||||
)
|
||||
).limit(10).all()
|
||||
|
||||
for m in machines:
|
||||
# Determine type: PC, Printer, or Equipment
|
||||
is_pc = m.pctypeid is not None
|
||||
is_printer = m.is_printer
|
||||
|
||||
# Calculate relevance - exact matches score higher
|
||||
relevance = 15
|
||||
if m.machinenumber and query.lower() == m.machinenumber.lower():
|
||||
relevance = 100
|
||||
elif m.hostname and query.lower() == m.hostname.lower():
|
||||
relevance = 100
|
||||
elif m.alias and query.lower() in m.alias.lower():
|
||||
relevance = 50
|
||||
|
||||
display_name = m.hostname if is_pc and m.hostname else m.machinenumber
|
||||
if m.alias and not is_pc:
|
||||
display_name = f"{m.machinenumber} ({m.alias})"
|
||||
|
||||
# Determine result type and URL
|
||||
if is_printer:
|
||||
result_type = 'printer'
|
||||
url = f"/printers/{m.machineid}"
|
||||
elif is_pc:
|
||||
result_type = 'pc'
|
||||
url = f"/pcs/{m.machineid}"
|
||||
else:
|
||||
result_type = 'machine'
|
||||
url = f"/machines/{m.machineid}"
|
||||
|
||||
# Get location - prefer machine's own location, fall back to parent machine's location
|
||||
location_name = None
|
||||
if m.location:
|
||||
location_name = m.location.locationname
|
||||
elif m.parent_relationships:
|
||||
# Check parent machines for location
|
||||
for rel in m.parent_relationships:
|
||||
if rel.parent_machine and rel.parent_machine.location:
|
||||
location_name = rel.parent_machine.location.locationname
|
||||
break
|
||||
|
||||
# Get machinetype from model (single source of truth)
|
||||
mt = m.derived_machinetype
|
||||
results.append({
|
||||
'type': result_type,
|
||||
'id': m.machineid,
|
||||
'title': display_name,
|
||||
'subtitle': mt.machinetype if mt else None,
|
||||
'location': location_name,
|
||||
'url': url,
|
||||
'relevance': relevance
|
||||
})
|
||||
|
||||
# Search Applications
|
||||
apps = Application.query.filter(
|
||||
Application.isactive == True,
|
||||
db.or_(
|
||||
Application.appname.ilike(search_term),
|
||||
Application.appdescription.ilike(search_term)
|
||||
)
|
||||
).limit(10).all()
|
||||
|
||||
for app in apps:
|
||||
relevance = 20
|
||||
if query.lower() == app.appname.lower():
|
||||
relevance = 100
|
||||
elif query.lower() in app.appname.lower():
|
||||
relevance = 50
|
||||
|
||||
results.append({
|
||||
'type': 'application',
|
||||
'id': app.appid,
|
||||
'title': app.appname,
|
||||
'subtitle': app.appdescription[:100] if app.appdescription else None,
|
||||
'url': f"/applications/{app.appid}",
|
||||
'relevance': relevance
|
||||
})
|
||||
|
||||
# Search Knowledge Base
|
||||
kb_articles = KnowledgeBase.query.filter(
|
||||
KnowledgeBase.isactive == True,
|
||||
db.or_(
|
||||
KnowledgeBase.shortdescription.ilike(search_term),
|
||||
KnowledgeBase.keywords.ilike(search_term)
|
||||
)
|
||||
).limit(20).all()
|
||||
|
||||
for kb in kb_articles:
|
||||
# Weight by clicks and keyword match
|
||||
relevance = 10 + (kb.clicks or 0) * 0.1
|
||||
if kb.keywords and query.lower() in kb.keywords.lower():
|
||||
relevance += 15
|
||||
|
||||
results.append({
|
||||
'type': 'knowledgebase',
|
||||
'id': kb.linkid,
|
||||
'title': kb.shortdescription,
|
||||
'subtitle': kb.application.appname if kb.application else None,
|
||||
'url': f"/knowledgebase/{kb.linkid}",
|
||||
'linkurl': kb.linkurl,
|
||||
'relevance': relevance
|
||||
})
|
||||
|
||||
# Search Printers (check if printers model exists)
|
||||
try:
|
||||
from shopdb.plugins.printers.models import Printer
|
||||
printers = Printer.query.filter(
|
||||
Printer.isactive == True,
|
||||
db.or_(
|
||||
Printer.printercsfname.ilike(search_term),
|
||||
Printer.printerwindowsname.ilike(search_term),
|
||||
Printer.serialnumber.ilike(search_term),
|
||||
Printer.fqdn.ilike(search_term)
|
||||
)
|
||||
).limit(10).all()
|
||||
|
||||
for p in printers:
|
||||
relevance = 15
|
||||
if p.printercsfname and query.lower() == p.printercsfname.lower():
|
||||
relevance = 100
|
||||
|
||||
display_name = p.printercsfname or p.printerwindowsname or f"Printer #{p.printerid}"
|
||||
|
||||
results.append({
|
||||
'type': 'printer',
|
||||
'id': p.printerid,
|
||||
'title': display_name,
|
||||
'subtitle': p.printerwindowsname if p.printercsfname else None,
|
||||
'url': f"/printers/{p.printerid}",
|
||||
'relevance': relevance
|
||||
})
|
||||
except ImportError:
|
||||
pass # Printers plugin not installed
|
||||
|
||||
# Sort by relevance (highest first)
|
||||
results.sort(key=lambda x: x['relevance'], reverse=True)
|
||||
|
||||
# Limit total results
|
||||
results = results[:30]
|
||||
|
||||
return success_response({
|
||||
'results': results,
|
||||
'query': query,
|
||||
'total': len(results)
|
||||
})
|
||||
Reference in New Issue
Block a user