Add application support teams with contacts
Replaces the legacy supportteams/appowners pair: supportteams (teamname unique, teamurl ServiceNow link) + supportteamcontacts (multiple named contacts with SSO per team, the people you reach out to), applications.supportteamid intact. Migration 7d18 migrates each legacy team owner into a contact, drops appowners, and has a validated downgrade. New /api/supportteams CRUD (admin writes, import-mode timestamps, teamname lookup), Support card on application detail, contacts column on the list, and a settings management page. IMPORT-API.md mapping updated to the concrete endpoints. 658 tests pass; live dev migration applied (24 teams / 24 contacts); fresh-install and downgrade round-trips verified on scratch DBs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ from flask_jwt_extended import jwt_required
|
||||
|
||||
from shopdb.extensions import db
|
||||
from shopdb.core.models import (
|
||||
Application, AppVersion, AppOwner, SupportTeam, AuditLog
|
||||
Application, AppVersion, AuditLog
|
||||
)
|
||||
from shopdb.utils.responses import (
|
||||
success_response,
|
||||
@@ -96,20 +96,8 @@ def list_applications():
|
||||
items, total = paginate_query(query, page, per_page)
|
||||
data = []
|
||||
for app in items:
|
||||
# to_dict already flattens supportteamname/teamurl/contacts.
|
||||
app_dict = app.to_dict()
|
||||
if app.supportteam:
|
||||
app_dict['supportteam'] = {
|
||||
'supportteamid': app.supportteam.supportteamid,
|
||||
'teamname': app.supportteam.teamname,
|
||||
'teamurl': app.supportteam.teamurl,
|
||||
'owner': {
|
||||
'appownerid': app.supportteam.owner.appownerid,
|
||||
'appowner': app.supportteam.owner.appowner,
|
||||
'sso': app.supportteam.owner.sso
|
||||
} if app.supportteam.owner else None
|
||||
}
|
||||
else:
|
||||
app_dict['supportteam'] = None
|
||||
app_dict['installedcount'] = _installed_count(app.appid)
|
||||
data.append(app_dict)
|
||||
|
||||
@@ -126,19 +114,6 @@ def get_application(app_id: int):
|
||||
return error_response(ErrorCodes.NOT_FOUND, 'Application not found', http_code=404)
|
||||
|
||||
data = app.to_dict()
|
||||
if app.supportteam:
|
||||
data['supportteam'] = {
|
||||
'supportteamid': app.supportteam.supportteamid,
|
||||
'teamname': app.supportteam.teamname,
|
||||
'teamurl': app.supportteam.teamurl,
|
||||
'owner': {
|
||||
'appownerid': app.supportteam.owner.appownerid,
|
||||
'appowner': app.supportteam.owner.appowner,
|
||||
'sso': app.supportteam.owner.sso
|
||||
} if app.supportteam.owner else None
|
||||
}
|
||||
else:
|
||||
data['supportteam'] = None
|
||||
data['versions'] = [v.to_dict() for v in app.versions.filter_by(isactive=True).order_by(AppVersion.version.desc()).all()]
|
||||
data['installedcount'] = _installed_count(app.appid)
|
||||
|
||||
@@ -466,69 +441,5 @@ def update_installed_app(machine_id: int, app_id: int):
|
||||
|
||||
return success_response(installed.to_dict(), message='Installation updated')
|
||||
|
||||
|
||||
# ---- Support Teams ----
|
||||
|
||||
@applications_bp.route('/supportteams', methods=['GET'])
|
||||
@jwt_required(optional=True)
|
||||
def list_support_teams():
|
||||
"""List all support teams."""
|
||||
teams = SupportTeam.query.filter_by(isactive=True).order_by(SupportTeam.teamname).all()
|
||||
data = []
|
||||
for team in teams:
|
||||
team_dict = team.to_dict()
|
||||
team_dict['owner'] = team.owner.appowner if team.owner else None
|
||||
data.append(team_dict)
|
||||
return success_response(data)
|
||||
|
||||
|
||||
@applications_bp.route('/supportteams', methods=['POST'])
|
||||
@jwt_required()
|
||||
@require_permission('applications.create')
|
||||
def create_support_team():
|
||||
"""Create a new support team."""
|
||||
data = request.get_json()
|
||||
if not data or not data.get('teamname'):
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, 'teamname is required')
|
||||
|
||||
team = SupportTeam(
|
||||
teamname=data['teamname'],
|
||||
teamurl=data.get('teamurl'),
|
||||
appownerid=data.get('appownerid')
|
||||
)
|
||||
|
||||
db.session.add(team)
|
||||
db.session.commit()
|
||||
|
||||
return success_response(team.to_dict(), message='Support team created', http_code=201)
|
||||
|
||||
|
||||
# ---- App Owners ----
|
||||
|
||||
@applications_bp.route('/appowners', methods=['GET'])
|
||||
@jwt_required(optional=True)
|
||||
def list_app_owners():
|
||||
"""List all application owners."""
|
||||
owners = AppOwner.query.filter_by(isactive=True).order_by(AppOwner.appowner).all()
|
||||
return success_response([o.to_dict() for o in owners])
|
||||
|
||||
|
||||
@applications_bp.route('/appowners', methods=['POST'])
|
||||
@jwt_required()
|
||||
@require_permission('applications.create')
|
||||
def create_app_owner():
|
||||
"""Create a new application owner."""
|
||||
data = request.get_json()
|
||||
if not data or not data.get('appowner'):
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, 'appowner is required')
|
||||
|
||||
owner = AppOwner(
|
||||
appowner=data['appowner'],
|
||||
sso=data.get('sso'),
|
||||
email=data.get('email')
|
||||
)
|
||||
|
||||
db.session.add(owner)
|
||||
db.session.commit()
|
||||
|
||||
return success_response(owner.to_dict(), message='App owner created', http_code=201)
|
||||
# Support teams + contacts now live in the supportteams blueprint
|
||||
# (/api/supportteams), replacing the legacy appowners pair.
|
||||
|
||||
Reference in New Issue
Block a user