"""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"" 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""