mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
Both are Django apps, and both pin `label` in apps.py — "stamp" and "review". django_migrations keys a row on (app_label, name), and app_label comes from that pin and not from the import path, so moving the directory does not orphan a single migration. All six stay where they are, untouched. products/stamphog -> products/stamp name = products.stamp.backend products/review_hog -> products/review name = products.review.backend StamphogScene/ -> StampScene/, and stamphogSceneLogic.ts with it 189 files follow. An earlier attempt at this move failed twice for reasons worth recording: `git mv A B` where B exists puts A INSIDE B, and a stray products/stamp/node_modules from an abandoned run was enough to make B exist — the app ended up nested two deep. And `git ls-files` does not list a file moved with plain mv and not yet staged, so a pass over "every tracked file" silently skips it; that is how StampScene.tsx kept importing ./stampSceneLogic after the file beside it had been renamed back. bin/check-imports-are-committed: all first-party imports across 15,843 files resolve. Co-authored-by: Hanzo Dev <[email protected]>
168 lines
6.0 KiB
Python
168 lines
6.0 KiB
Python
"""
|
|
Facade for stamp.
|
|
|
|
The ONLY module other products are allowed to import.
|
|
Accept ids / frozen dataclasses, call into models, return frozen
|
|
dataclasses. Never return ORM instances or import DRF.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from ..models import DigestChannel, DigestRun, PullRequest, ReviewRun, StampRepoConfig
|
|
from . import contracts
|
|
from .enums import ChannelResolutionSource, DigestRunStatus, ReviewRunStatus, ReviewVerdict
|
|
|
|
|
|
def _repo_config_to_dto(obj: StampRepoConfig) -> contracts.RepoConfigDTO:
|
|
return contracts.RepoConfigDTO(
|
|
id=obj.id,
|
|
team_id=obj.team_id,
|
|
provider=obj.provider,
|
|
repository=obj.repository,
|
|
enabled=obj.enabled,
|
|
installation_id=obj.installation_id,
|
|
digest_enabled=obj.digest_enabled,
|
|
created_at=obj.created_at,
|
|
updated_at=obj.updated_at,
|
|
)
|
|
|
|
|
|
def _pull_request_to_dto(obj: PullRequest) -> contracts.PullRequestDTO:
|
|
return contracts.PullRequestDTO(
|
|
id=obj.id,
|
|
team_id=obj.team_id,
|
|
repo_config_id=obj.repo_config_id,
|
|
repository=obj.repo_config.repository,
|
|
pr_number=obj.pr_number,
|
|
pr_url=obj.pr_url,
|
|
title=obj.title,
|
|
author_login=obj.author_login,
|
|
head_branch=obj.head_branch,
|
|
body_excerpt=obj.body_excerpt,
|
|
additions=obj.additions,
|
|
deletions=obj.deletions,
|
|
changed_files=obj.changed_files,
|
|
audience_key=obj.audience_key,
|
|
merge_commit_sha=obj.merge_commit_sha,
|
|
merged_at=obj.merged_at,
|
|
digest_run_id=obj.digest_run_id,
|
|
posted_comment_id=obj.posted_comment_id,
|
|
created_at=obj.created_at,
|
|
updated_at=obj.updated_at,
|
|
)
|
|
|
|
|
|
def _digest_channel_to_dto(obj: DigestChannel) -> contracts.DigestChannelDTO:
|
|
return contracts.DigestChannelDTO(
|
|
id=obj.id,
|
|
team_id=obj.team_id,
|
|
audience_key=obj.audience_key,
|
|
slack_integration_id=obj.slack_integration_id,
|
|
slack_channel_id=obj.slack_channel_id,
|
|
slack_channel_name=obj.slack_channel_name,
|
|
enabled=obj.enabled,
|
|
resolution_source=ChannelResolutionSource(obj.resolution_source),
|
|
last_digest_at=obj.last_digest_at,
|
|
created_at=obj.created_at,
|
|
updated_at=obj.updated_at,
|
|
)
|
|
|
|
|
|
def _digest_run_to_dto(obj: DigestRun) -> contracts.DigestRunDTO:
|
|
return contracts.DigestRunDTO(
|
|
id=obj.id,
|
|
team_id=obj.team_id,
|
|
digest_channel_id=obj.digest_channel_id,
|
|
status=DigestRunStatus(obj.status),
|
|
pr_count=obj.pr_count,
|
|
summary=obj.summary,
|
|
slack_message_ts=obj.slack_message_ts,
|
|
error=obj.error,
|
|
created_at=obj.created_at,
|
|
posted_at=obj.posted_at,
|
|
)
|
|
|
|
|
|
def _review_run_to_dto(obj: ReviewRun) -> contracts.ReviewRunDTO:
|
|
return contracts.ReviewRunDTO(
|
|
id=obj.id,
|
|
team_id=obj.team_id,
|
|
pull_request_id=obj.pull_request_id,
|
|
repository=obj.pull_request.repo_config.repository,
|
|
pr_number=obj.pull_request.pr_number,
|
|
pr_url=obj.pull_request.pr_url,
|
|
head_sha=obj.head_sha,
|
|
status=ReviewRunStatus(obj.status),
|
|
verdict=ReviewVerdict(obj.verdict),
|
|
delivery_id=obj.delivery_id,
|
|
gate_result=obj.gate_result,
|
|
output=obj.output,
|
|
error=obj.error,
|
|
created_at=obj.created_at,
|
|
updated_at=obj.updated_at,
|
|
completed_at=obj.completed_at,
|
|
)
|
|
|
|
|
|
def get_repo_config(team_id: int, repository: str) -> contracts.RepoConfigDTO | None:
|
|
obj = StampRepoConfig.objects.for_team(team_id).filter(repository=repository).first()
|
|
return _repo_config_to_dto(obj) if obj is not None else None
|
|
|
|
|
|
def has_reviewable_repo_config(team_id: int) -> bool:
|
|
"""Whether the team has at least one enabled repo config that hosted reviews can run on.
|
|
|
|
The config must have been bound through the authenticated sync flow: a non-blank
|
|
installation_id and a connecting user, because the sandbox LLM credential is minted under
|
|
that user and reviews fail closed without one. The Code review scene disables its Stamp
|
|
inbox toggle when this is false, since the toggle would have nothing to act on.
|
|
"""
|
|
return (
|
|
StampRepoConfig.objects.for_team(team_id)
|
|
.filter(enabled=True, connected_by_user_id__isnull=False)
|
|
.exclude(installation_id="")
|
|
.exists()
|
|
)
|
|
|
|
|
|
def get_review_run(team_id: int, review_run_id: str) -> contracts.ReviewRunDTO | None:
|
|
obj = (
|
|
ReviewRun.objects.for_team(team_id).filter(id=review_run_id).select_related("pull_request__repo_config").first()
|
|
)
|
|
return _review_run_to_dto(obj) if obj is not None else None
|
|
|
|
|
|
def create_review_run(
|
|
*,
|
|
team_id: int,
|
|
pull_request_id: str,
|
|
head_sha: str,
|
|
delivery_id: str | None = None,
|
|
) -> contracts.ReviewRunDTO:
|
|
# Resolve the PR through the team scope rather than trusting the raw FK id: a caller passing
|
|
# another team's pull_request_id would otherwise create a run whose reads follow the FK across
|
|
# the tenant boundary (get_review_run would surface the other team's repo and PR details).
|
|
pull_request = PullRequest.objects.for_team(team_id).get(id=pull_request_id)
|
|
obj = ReviewRun.objects.for_team(team_id).create(
|
|
team_id=team_id,
|
|
pull_request=pull_request,
|
|
head_sha=head_sha,
|
|
delivery_id=delivery_id,
|
|
)
|
|
return _review_run_to_dto(obj)
|
|
|
|
|
|
def get_digest_channel(team_id: int, digest_channel_id: str) -> contracts.DigestChannelDTO | None:
|
|
obj = DigestChannel.objects.for_team(team_id).filter(id=digest_channel_id).first()
|
|
return _digest_channel_to_dto(obj) if obj is not None else None
|
|
|
|
|
|
def get_digest_run(team_id: int, digest_run_id: str) -> contracts.DigestRunDTO | None:
|
|
obj = DigestRun.objects.for_team(team_id).filter(id=digest_run_id).first()
|
|
return _digest_run_to_dto(obj) if obj is not None else None
|
|
|
|
|
|
def get_pull_request(team_id: int, pull_request_id: str) -> contracts.PullRequestDTO | None:
|
|
obj = PullRequest.objects.for_team(team_id).filter(id=pull_request_id).select_related("repo_config").first()
|
|
return _pull_request_to_dto(obj) if obj is not None else None
|