AppVersion gains sha256 and silentargs so the endpoint update_app push can pin exact installer bytes and carry per-version silent switches; Application gains installersha256 for the app-level install source. New ShopDB-hosted script library (Script model plus scripts API) backs run_script delivery, with the body fetched by digest on push rather than pre-staged or sent over the command wire. Two idempotent migrations with real downgrades. Script added to the shopdb.api contract surface for plugin use.
109 lines
4.3 KiB
Python
109 lines
4.3 KiB
Python
"""Application tracking models."""
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
from shopdb.extensions import db
|
|
from .base import BaseModel
|
|
|
|
|
|
def _utcnow():
|
|
# naive UTC to match the other DB DateTime columns (stored without tzinfo)
|
|
return datetime.now(timezone.utc).replace(tzinfo=None)
|
|
# SupportTeam / SupportTeamContact live in supportteam.py; imported by the
|
|
# models package so the Application.supportteam relationship resolves.
|
|
|
|
|
|
class Application(BaseModel):
|
|
"""Application catalog."""
|
|
__tablename__ = 'applications'
|
|
|
|
appid = db.Column(db.Integer, primary_key=True)
|
|
appname = db.Column(db.String(100), unique=True, nullable=False)
|
|
appdescription = db.Column(db.String(255))
|
|
supportteamid = db.Column(db.Integer, db.ForeignKey('supportteams.supportteamid'))
|
|
isinstallable = db.Column(db.Boolean, default=False)
|
|
applicationnotes = db.Column(db.Text)
|
|
installpath = db.Column(db.String(255))
|
|
applicationlink = db.Column(db.String(512))
|
|
documentationpath = db.Column(db.String(512))
|
|
ishidden = db.Column(db.Boolean, default=False)
|
|
isprinter = db.Column(db.Boolean, default=False)
|
|
islicenced = db.Column(db.Boolean, default=False)
|
|
isrequired = db.Column(
|
|
db.Boolean, default=False,
|
|
comment='Required on all PCs (drives the software-compliance report)'
|
|
)
|
|
image = db.Column(db.String(255))
|
|
# Digest of the app-level uploaded installer (installpath). Lets the
|
|
# endpoint update_app push resolve an install source by app alone, with no
|
|
# AppVersion row: the agent fetches the pinned bytes and verifies this hash.
|
|
# Stamped on package upload; null when installpath is a site share path.
|
|
installersha256 = db.Column(db.String(64))
|
|
|
|
# Relationships
|
|
supportteam = db.relationship('SupportTeam', back_populates='applications')
|
|
versions = db.relationship('AppVersion', back_populates='application', lazy='dynamic')
|
|
|
|
def to_dict(self):
|
|
"""Serialize, flattening the support team + its active contacts.
|
|
|
|
Emits supportteamname, teamurl, and the team's active contacts so the
|
|
frontend needs a single call to render the Support card.
|
|
"""
|
|
result = super().to_dict()
|
|
team = self.supportteam
|
|
result['supportteamname'] = team.teamname if team else None
|
|
result['teamurl'] = team.teamurl if team else None
|
|
result['contacts'] = [
|
|
{'name': c.name, 'sso': c.sso}
|
|
for c in team.active_contacts()
|
|
] if team else []
|
|
return result
|
|
|
|
def __repr__(self):
|
|
return f"<Application {self.appname}>"
|
|
|
|
|
|
class AppVersion(db.Model):
|
|
"""Application version tracking."""
|
|
__tablename__ = 'appversions'
|
|
|
|
appversionid = db.Column(db.Integer, primary_key=True)
|
|
appid = db.Column(db.Integer, db.ForeignKey('applications.appid'), nullable=False)
|
|
version = db.Column(db.String(50), nullable=False)
|
|
releasedate = db.Column(db.Date)
|
|
notes = db.Column(db.String(255))
|
|
dateadded = db.Column(db.DateTime, default=_utcnow)
|
|
isactive = db.Column(db.Boolean, default=True)
|
|
# Delivery metadata for the endpoint installer push. sha256 pins the exact
|
|
# bytes the agent must fetch (warn-not-block for now; hardened later);
|
|
# silentargs carries this version's unattended-install switches because
|
|
# homegrown EXEs vary by developer (MSI is uniform via msiexec).
|
|
sha256 = db.Column(db.String(64))
|
|
silentargs = db.Column(db.String(255))
|
|
|
|
# Relationships
|
|
application = db.relationship('Application', back_populates='versions')
|
|
|
|
# Unique constraint on app + version
|
|
__table_args__ = (
|
|
db.UniqueConstraint('appid', 'version', name='uq_app_version'),
|
|
)
|
|
|
|
def to_dict(self):
|
|
"""Convert to dictionary."""
|
|
return {
|
|
'appversionid': self.appversionid,
|
|
'appid': self.appid,
|
|
'version': self.version,
|
|
'releasedate': self.releasedate.isoformat() if self.releasedate else None,
|
|
'notes': self.notes,
|
|
'dateadded': self.dateadded.isoformat() + 'Z' if self.dateadded else None,
|
|
'isactive': self.isactive,
|
|
'sha256': self.sha256,
|
|
'silentargs': self.silentargs
|
|
}
|
|
|
|
def __repr__(self):
|
|
return f"<AppVersion {self.application.appname if self.application else self.appid} v{self.version}>"
|