feat: safe area fix, delete from repo, add Mistral provider
Release APK / build (push) Has been cancelled
Release APK / build (push) Has been cancelled
- Wrap root layout with SafeAreaProvider, use useSafeAreaInsets in all screens so FAB/action bar/scroll content clear Android nav bar - Add deleteIncidentFromGit() (Gitea/GitHub/GitLab) — detail screen now offers Cancel / Local only / Local + Repo when incident was pushed - Add Mistral as AI provider (OpenAI-compat, api.mistral.ai/v1, mistral-small-latest default) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+101
@@ -18,6 +18,11 @@ export interface PushResult {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface DeleteResult {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
const TIMEOUT_MS = 15_000;
|
||||
|
||||
export async function loadGitConfig(): Promise<GitConfig | null> {
|
||||
@@ -66,6 +71,20 @@ function fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteIncidentFromGit(
|
||||
incident: Incident,
|
||||
config: GitConfig
|
||||
): Promise<DeleteResult> {
|
||||
switch (config.provider) {
|
||||
case "gitea":
|
||||
return deleteGitea(incident, config);
|
||||
case "github":
|
||||
return deleteGitHub(incident, config);
|
||||
case "gitlab":
|
||||
return deleteGitLab(incident, config);
|
||||
}
|
||||
}
|
||||
|
||||
export async function pushIncidentToGit(
|
||||
incident: Incident,
|
||||
config: GitConfig,
|
||||
@@ -216,6 +235,88 @@ async function pushGitHub(
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteGitea(incident: Incident, config: GitConfig): Promise<DeleteResult> {
|
||||
if (!config.url) return { success: false, error: "Gitea instance URL is not set." };
|
||||
const filepath = incidentToFilename(incident);
|
||||
const apiBase = config.url.replace(/\/$/, "");
|
||||
const endpoint = `${apiBase}/api/v1/repos/${config.owner}/${config.repo}/contents/${filepath}`;
|
||||
try {
|
||||
const existing = await fetchWithTimeout(endpoint, {
|
||||
headers: { Authorization: `token ${config.token}` },
|
||||
});
|
||||
if (existing.status === 404) return { success: true };
|
||||
if (!existing.ok) return { success: false, error: `GET ${existing.status}` };
|
||||
const data = await existing.json() as { sha?: string };
|
||||
if (!data.sha) return { success: false, error: "No SHA returned by API." };
|
||||
|
||||
const res = await fetchWithTimeout(endpoint, {
|
||||
method: "DELETE",
|
||||
headers: { Authorization: `token ${config.token}`, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ message: `remove: ${incident.title}`, sha: data.sha, branch: "main" }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const body = await res.text();
|
||||
return { success: false, error: `HTTP ${res.status}: ${body.slice(0, 200)}` };
|
||||
}
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.name === "AbortError") return { success: false, error: `Request timed out after ${TIMEOUT_MS / 1000}s.` };
|
||||
return { success: false, error: e instanceof Error ? e.message : String(e) };
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteGitHub(incident: Incident, config: GitConfig): Promise<DeleteResult> {
|
||||
const filepath = incidentToFilename(incident);
|
||||
const endpoint = `https://api.github.com/repos/${config.owner}/${config.repo}/contents/${filepath}`;
|
||||
try {
|
||||
const existing = await fetchWithTimeout(endpoint, {
|
||||
headers: { Authorization: `Bearer ${config.token}`, Accept: "application/vnd.github+json" },
|
||||
});
|
||||
if (existing.status === 404) return { success: true };
|
||||
if (!existing.ok) return { success: false, error: `GET ${existing.status}` };
|
||||
const data = await existing.json() as { sha?: string };
|
||||
if (!data.sha) return { success: false, error: "No SHA returned by API." };
|
||||
|
||||
const res = await fetchWithTimeout(endpoint, {
|
||||
method: "DELETE",
|
||||
headers: { Authorization: `Bearer ${config.token}`, Accept: "application/vnd.github+json", "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ message: `remove: ${incident.title}`, sha: data.sha }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const body = await res.text();
|
||||
return { success: false, error: `HTTP ${res.status}: ${body.slice(0, 200)}` };
|
||||
}
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.name === "AbortError") return { success: false, error: `Request timed out after ${TIMEOUT_MS / 1000}s.` };
|
||||
return { success: false, error: e instanceof Error ? e.message : String(e) };
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteGitLab(incident: Incident, config: GitConfig): Promise<DeleteResult> {
|
||||
const filepath = incidentToFilename(incident);
|
||||
const apiBase = (config.url ?? "https://gitlab.com").replace(/\/$/, "");
|
||||
const projectId = encodeURIComponent(`${config.owner}/${config.repo}`);
|
||||
const encodedPath = encodeURIComponent(filepath);
|
||||
const endpoint = `${apiBase}/api/v4/projects/${projectId}/repository/files/${encodedPath}`;
|
||||
try {
|
||||
const res = await fetchWithTimeout(endpoint, {
|
||||
method: "DELETE",
|
||||
headers: { "PRIVATE-TOKEN": config.token, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ branch: "main", commit_message: `remove: ${incident.title}` }),
|
||||
});
|
||||
if (res.status === 404) return { success: true };
|
||||
if (!res.ok) {
|
||||
const body = await res.text();
|
||||
return { success: false, error: `HTTP ${res.status}: ${body.slice(0, 200)}` };
|
||||
}
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.name === "AbortError") return { success: false, error: `Request timed out after ${TIMEOUT_MS / 1000}s.` };
|
||||
return { success: false, error: e instanceof Error ? e.message : String(e) };
|
||||
}
|
||||
}
|
||||
|
||||
async function pushGitLab(
|
||||
incident: Incident,
|
||||
config: GitConfig,
|
||||
|
||||
Reference in New Issue
Block a user