Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 83fe089ea5 | |||
| 947ae5917c | |||
| 1437e99cbb |
@@ -42,15 +42,13 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Set up Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Accept Android SDK licenses
|
||||
run: yes | sdkmanager --licenses || true
|
||||
run: yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses || true
|
||||
|
||||
- name: Install SDK components
|
||||
run: |
|
||||
sdkmanager "platform-tools" "platforms;android-35" "build-tools;35.0.0"
|
||||
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" \
|
||||
"platform-tools" "platforms;android-35" "build-tools;35.0.0"
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v4
|
||||
|
||||
+10
-50
@@ -10,10 +10,9 @@ import {
|
||||
import { useLocalSearchParams, router, useNavigation } from "expo-router";
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
import * as SecureStore from "expo-secure-store";
|
||||
import { getIncidentById, updateIncident, deleteIncident } from "@/lib/db";
|
||||
import { getIncidentById, updateIncident, deleteIncident, markIncidentPushed } from "@/lib/db";
|
||||
import { renderMarkdown } from "@/lib/markdown";
|
||||
import { pushIncidentToGit } from "@/lib/git";
|
||||
import type { GitProvider } from "@/lib/git";
|
||||
import { pushIncidentToGit, loadGitConfig } from "@/lib/git";
|
||||
import type { Incident } from "@/types/incident";
|
||||
import { Colors } from "@/constants/theme";
|
||||
|
||||
@@ -81,60 +80,21 @@ export default function IncidentDetailScreen() {
|
||||
|
||||
async function handleGitPush() {
|
||||
if (!incident) return;
|
||||
const [provider, template, giteaUrl, giteaToken, giteaOwner, giteaRepo, githubToken, githubOwner, githubRepo, gitlabUrl, gitlabToken, gitlabOwner, gitlabRepo] =
|
||||
await Promise.all([
|
||||
SecureStore.getItemAsync("git_provider"),
|
||||
const [config, template] = await Promise.all([
|
||||
loadGitConfig(),
|
||||
SecureStore.getItemAsync("md_template"),
|
||||
SecureStore.getItemAsync("gitea_url"),
|
||||
SecureStore.getItemAsync("gitea_token"),
|
||||
SecureStore.getItemAsync("gitea_owner"),
|
||||
SecureStore.getItemAsync("gitea_repo"),
|
||||
SecureStore.getItemAsync("github_token"),
|
||||
SecureStore.getItemAsync("github_owner"),
|
||||
SecureStore.getItemAsync("github_repo"),
|
||||
SecureStore.getItemAsync("gitlab_url"),
|
||||
SecureStore.getItemAsync("gitlab_token"),
|
||||
SecureStore.getItemAsync("gitlab_owner"),
|
||||
SecureStore.getItemAsync("gitlab_repo"),
|
||||
]);
|
||||
|
||||
const p = (provider ?? "gitea") as GitProvider;
|
||||
|
||||
let url: string | undefined;
|
||||
let token: string | null;
|
||||
let owner: string | null;
|
||||
let repo: string | null;
|
||||
|
||||
if (p === "gitea") {
|
||||
url = giteaUrl ?? undefined;
|
||||
token = giteaToken;
|
||||
owner = giteaOwner;
|
||||
repo = giteaRepo;
|
||||
} else if (p === "github") {
|
||||
token = githubToken;
|
||||
owner = githubOwner;
|
||||
repo = githubRepo;
|
||||
} else {
|
||||
url = gitlabUrl ?? undefined;
|
||||
token = gitlabToken;
|
||||
owner = gitlabOwner;
|
||||
repo = gitlabRepo;
|
||||
}
|
||||
|
||||
const needsUrl = p === "gitea" || p === "gitlab";
|
||||
if (!token || !owner || !repo || (needsUrl && !url)) {
|
||||
Alert.alert("Git not configured", `Set up ${p.charAt(0).toUpperCase() + p.slice(1)} in Settings first.`);
|
||||
if (!config) {
|
||||
Alert.alert("Git not configured", "Set up a git provider in Settings first.");
|
||||
return;
|
||||
}
|
||||
|
||||
setPushing(true);
|
||||
const result = await pushIncidentToGit(
|
||||
incident,
|
||||
{ provider: p, url, token, owner, repo },
|
||||
template ?? undefined
|
||||
);
|
||||
const result = await pushIncidentToGit(incident, config, template ?? undefined);
|
||||
setPushing(false);
|
||||
if (result.success) {
|
||||
const now = new Date().toISOString();
|
||||
await markIncidentPushed(incident.id);
|
||||
setIncident((prev) => prev ? { ...prev, gitPushedAt: now } : prev);
|
||||
Alert.alert("Pushed", result.url ?? "Push successful.");
|
||||
} else {
|
||||
Alert.alert("Push failed", result.error ?? "Unknown error");
|
||||
|
||||
+255
-20
@@ -5,16 +5,32 @@ import {
|
||||
FlatList,
|
||||
Pressable,
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
} from "react-native";
|
||||
import { router, useFocusEffect } from "expo-router";
|
||||
import { getIncidents } from "@/lib/db";
|
||||
import * as SecureStore from "expo-secure-store";
|
||||
import { getIncidents, markIncidentPushed } from "@/lib/db";
|
||||
import { pushIncidentToGit, loadGitConfig } from "@/lib/git";
|
||||
import type { GitConfig } from "@/lib/git";
|
||||
import { useSettings } from "@/hooks/useSettings";
|
||||
import type { Incident } from "@/types/incident";
|
||||
import { Colors } from "@/constants/theme";
|
||||
|
||||
function gitDotColor(inc: Incident): string {
|
||||
if (!inc.gitPushedAt) return Colors.danger;
|
||||
if (inc.gitPushedAt < inc.updatedAt) return Colors.warning;
|
||||
return Colors.success;
|
||||
}
|
||||
|
||||
export default function HomeScreen() {
|
||||
const [incidents, setIncidents] = useState<Incident[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [gitConfig, setGitConfig] = useState<GitConfig | null>(null);
|
||||
const [mdTemplate, setMdTemplate] = useState<string | undefined>(undefined);
|
||||
const [selectionMode, setSelectionMode] = useState(false);
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
const [bulkPushing, setBulkPushing] = useState(false);
|
||||
const [bulkProgress, setBulkProgress] = useState({ done: 0, total: 0 });
|
||||
const { settings, ready } = useSettings();
|
||||
const didRedirect = useRef(false);
|
||||
|
||||
@@ -28,24 +44,99 @@ export default function HomeScreen() {
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
loadIncidents();
|
||||
load();
|
||||
}, [])
|
||||
);
|
||||
|
||||
async function loadIncidents() {
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
const data = await getIncidents(10);
|
||||
const [data, config, tpl] = await Promise.all([
|
||||
getIncidents(100),
|
||||
loadGitConfig(),
|
||||
SecureStore.getItemAsync("md_template"),
|
||||
]);
|
||||
setIncidents(data);
|
||||
setGitConfig(config);
|
||||
setMdTemplate(tpl ?? undefined);
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
function enterSelectionMode(id: string) {
|
||||
setSelectionMode(true);
|
||||
setSelected(new Set([id]));
|
||||
}
|
||||
|
||||
function toggleSelection(id: string) {
|
||||
setSelected((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(id)) next.delete(id);
|
||||
else next.add(id);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
function exitSelectionMode() {
|
||||
setSelectionMode(false);
|
||||
setSelected(new Set());
|
||||
}
|
||||
|
||||
function selectAllUnpushed() {
|
||||
const ids = incidents
|
||||
.filter((i) => !i.gitPushedAt || i.gitPushedAt < i.updatedAt)
|
||||
.map((i) => i.id);
|
||||
setSelected(new Set(ids));
|
||||
}
|
||||
|
||||
const unpushedCount = incidents.filter(
|
||||
(i) => !i.gitPushedAt || i.gitPushedAt < i.updatedAt
|
||||
).length;
|
||||
|
||||
async function handleBulkPush() {
|
||||
if (!gitConfig) {
|
||||
Alert.alert("Git not configured", "Set up a git provider in Settings first.");
|
||||
return;
|
||||
}
|
||||
const toPush = incidents.filter((i) => selected.has(i.id));
|
||||
if (toPush.length === 0) return;
|
||||
|
||||
setBulkPushing(true);
|
||||
setBulkProgress({ done: 0, total: toPush.length });
|
||||
|
||||
let ok = 0;
|
||||
let fail = 0;
|
||||
const now = new Date().toISOString();
|
||||
|
||||
for (let i = 0; i < toPush.length; i++) {
|
||||
const inc = toPush[i];
|
||||
const result = await pushIncidentToGit(inc, gitConfig, mdTemplate);
|
||||
if (result.success) {
|
||||
await markIncidentPushed(inc.id);
|
||||
setIncidents((prev) =>
|
||||
prev.map((x) => (x.id === inc.id ? { ...x, gitPushedAt: now } : x))
|
||||
);
|
||||
ok++;
|
||||
} else {
|
||||
fail++;
|
||||
}
|
||||
setBulkProgress({ done: i + 1, total: toPush.length });
|
||||
}
|
||||
|
||||
setBulkPushing(false);
|
||||
exitSelectionMode();
|
||||
|
||||
if (fail === 0) {
|
||||
Alert.alert("Done", `${ok} incident${ok > 1 ? "s" : ""} pushed.`);
|
||||
} else {
|
||||
Alert.alert("Partial", `${ok} pushed, ${fail} failed.`);
|
||||
}
|
||||
}
|
||||
|
||||
const gitConfigured = gitConfig !== null;
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: Colors.bg }}>
|
||||
{loading ? (
|
||||
<ActivityIndicator
|
||||
color={Colors.primary}
|
||||
style={{ marginTop: 48 }}
|
||||
/>
|
||||
<ActivityIndicator color={Colors.primary} style={{ marginTop: 48 }} />
|
||||
) : (
|
||||
<FlatList
|
||||
data={incidents}
|
||||
@@ -63,19 +154,29 @@ export default function HomeScreen() {
|
||||
No incidents. Press + to log one.
|
||||
</Text>
|
||||
}
|
||||
renderItem={({ item }) => (
|
||||
renderItem={({ item }) => {
|
||||
const isSelected = selected.has(item.id);
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() => router.push(`/incident/${item.id}`)}
|
||||
onPress={() =>
|
||||
selectionMode
|
||||
? toggleSelection(item.id)
|
||||
: router.push(`/incident/${item.id}`)
|
||||
}
|
||||
onLongPress={() => !selectionMode && enterSelectionMode(item.id)}
|
||||
style={{
|
||||
backgroundColor: Colors.surface,
|
||||
backgroundColor: isSelected ? Colors.surface2 : Colors.surface,
|
||||
borderRadius: 8,
|
||||
padding: 16,
|
||||
marginBottom: 10,
|
||||
borderLeftWidth: 3,
|
||||
borderLeftColor:
|
||||
item.status === "open" ? Colors.warning : Colors.success,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<View style={{ flex: 1 }}>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
@@ -95,16 +196,24 @@ export default function HomeScreen() {
|
||||
>
|
||||
{item.title || "Untitled"}
|
||||
</Text>
|
||||
<View style={{ flexDirection: "row", alignItems: "center", marginLeft: 8, gap: 6 }}>
|
||||
{gitConfigured && !selectionMode && (
|
||||
<View
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
backgroundColor: gitDotColor(item),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<View
|
||||
style={{
|
||||
backgroundColor:
|
||||
item.status === "open"
|
||||
? "#f59e0b20"
|
||||
: "#88D65620",
|
||||
item.status === "open" ? "#f59e0b20" : "#88D65620",
|
||||
borderRadius: 4,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 2,
|
||||
marginLeft: 8,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
@@ -122,18 +231,46 @@ export default function HomeScreen() {
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<Text
|
||||
style={{ color: Colors.text2, fontSize: 12 }}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.service || "—"} · {item.createdAt.slice(0, 10)}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{selectionMode && (
|
||||
<View
|
||||
style={{
|
||||
width: 22,
|
||||
height: 22,
|
||||
borderRadius: 11,
|
||||
borderWidth: 2,
|
||||
borderColor: isSelected ? Colors.primary : Colors.border,
|
||||
backgroundColor: isSelected ? Colors.primary : "transparent",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginLeft: 14,
|
||||
}}
|
||||
>
|
||||
{isSelected && (
|
||||
<Text
|
||||
style={{ color: Colors.bg, fontSize: 12, fontWeight: "700" }}
|
||||
>
|
||||
✓
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* FAB */}
|
||||
{/* FAB — hidden in selection mode */}
|
||||
{!selectionMode && (
|
||||
<Pressable
|
||||
onPress={() => router.push("/new")}
|
||||
style={{
|
||||
@@ -149,12 +286,110 @@ export default function HomeScreen() {
|
||||
elevation: 6,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{ color: "#fff", fontSize: 28, lineHeight: 32 }}
|
||||
<Text style={{ color: "#fff", fontSize: 28, lineHeight: 32 }}>+</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
|
||||
{/* Selection mode action bar */}
|
||||
{selectionMode && (
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: Colors.surface,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: Colors.border,
|
||||
padding: 12,
|
||||
gap: 10,
|
||||
}}
|
||||
>
|
||||
+
|
||||
{/* Quick-select shortcuts */}
|
||||
<View style={{ flexDirection: "row", gap: 8 }}>
|
||||
{gitConfigured && unpushedCount > 0 && (
|
||||
<Pressable
|
||||
onPress={selectAllUnpushed}
|
||||
style={{
|
||||
flex: 1,
|
||||
borderRadius: 6,
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.border,
|
||||
padding: 8,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: Colors.text2, fontSize: 12, fontWeight: "600" }}>
|
||||
Select unpushed ({unpushedCount})
|
||||
</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
<Pressable
|
||||
onPress={() => setSelected(new Set(incidents.map((i) => i.id)))}
|
||||
style={{
|
||||
flex: 1,
|
||||
borderRadius: 6,
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.border,
|
||||
padding: 8,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: Colors.text2, fontSize: 12, fontWeight: "600" }}>
|
||||
Select all ({incidents.length})
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{/* Main actions */}
|
||||
<View style={{ flexDirection: "row", gap: 10 }}>
|
||||
<Pressable
|
||||
onPress={exitSelectionMode}
|
||||
style={{
|
||||
flex: 1,
|
||||
borderRadius: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.border,
|
||||
padding: 14,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: Colors.text2, fontSize: 15, fontWeight: "600" }}>
|
||||
Cancel
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={handleBulkPush}
|
||||
disabled={selected.size === 0 || bulkPushing || !gitConfigured}
|
||||
style={{
|
||||
flex: 2,
|
||||
borderRadius: 8,
|
||||
backgroundColor:
|
||||
selected.size === 0 || !gitConfigured
|
||||
? Colors.surface2
|
||||
: Colors.primary,
|
||||
padding: 14,
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color:
|
||||
selected.size === 0 || !gitConfigured
|
||||
? Colors.textDim
|
||||
: Colors.bg,
|
||||
fontSize: 15,
|
||||
fontWeight: "700",
|
||||
}}
|
||||
>
|
||||
{bulkPushing
|
||||
? `Pushing ${bulkProgress.done}/${bulkProgress.total}…`
|
||||
: `Push ${selected.size > 0 ? selected.size : ""} selected`}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
+9
-8
@@ -16,6 +16,7 @@ import {
|
||||
updateIncident,
|
||||
getIncidentById,
|
||||
getDistinctServices,
|
||||
getMaxTitleNumber,
|
||||
} from "@/lib/db";
|
||||
import { Colors } from "@/constants/theme";
|
||||
import { useVoice } from "@/hooks/useVoice";
|
||||
@@ -50,13 +51,6 @@ const LABEL_STYLE = {
|
||||
|
||||
type VoiceField = "title" | "service" | "symptom" | "rootCause" | "fix";
|
||||
|
||||
function incrementTemplate(template: string): string {
|
||||
const match = template.match(/^([\s\S]*?)(\d+)$/);
|
||||
if (!match) return template;
|
||||
const [, prefix, numStr] = match;
|
||||
const next = (parseInt(numStr, 10) + 1).toString().padStart(numStr.length, "0");
|
||||
return prefix + next;
|
||||
}
|
||||
|
||||
export default function NewIncidentScreen() {
|
||||
const { editId } = useLocalSearchParams<{ editId?: string }>();
|
||||
@@ -180,7 +174,14 @@ export default function NewIncidentScreen() {
|
||||
await createIncident(form);
|
||||
const tpl = await SecureStore.getItemAsync("pref_title_template");
|
||||
if (tpl) {
|
||||
await SecureStore.setItemAsync("pref_title_template", incrementTemplate(tpl));
|
||||
const match = tpl.match(/^([\s\S]*?)(\d+)$/);
|
||||
if (match) {
|
||||
const [, prefix, numStr] = match;
|
||||
const dbMax = await getMaxTitleNumber(prefix);
|
||||
const tplNum = parseInt(numStr, 10);
|
||||
const next = (Math.max(dbMax, tplNum) + 1).toString().padStart(numStr.length, "0");
|
||||
await SecureStore.setItemAsync("pref_title_template", prefix + next);
|
||||
}
|
||||
}
|
||||
}
|
||||
router.back();
|
||||
|
||||
@@ -45,6 +45,13 @@ async function migrate(db: SQLite.SQLiteDatabase): Promise<void> {
|
||||
INSERT INTO schema_version (version) VALUES (1);
|
||||
`);
|
||||
}
|
||||
|
||||
if (current < 2) {
|
||||
await db.execAsync(`
|
||||
ALTER TABLE incidents ADD COLUMN git_pushed_at TEXT;
|
||||
INSERT INTO schema_version (version) VALUES (2);
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
// --- row mapper ---
|
||||
@@ -60,6 +67,7 @@ interface IncidentRow {
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
tags: string;
|
||||
git_pushed_at: string | null;
|
||||
}
|
||||
|
||||
function rowToIncident(row: IncidentRow): Incident {
|
||||
@@ -74,6 +82,7 @@ function rowToIncident(row: IncidentRow): Incident {
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
tags: JSON.parse(row.tags) as string[],
|
||||
gitPushedAt: row.git_pushed_at ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -107,6 +116,7 @@ export async function createIncident(draft: IncidentDraft): Promise<Incident> {
|
||||
id,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
gitPushedAt: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -163,6 +173,28 @@ export async function deleteIncident(id: string): Promise<void> {
|
||||
await db.runAsync("DELETE FROM incidents WHERE id = ?", [id]);
|
||||
}
|
||||
|
||||
export async function markIncidentPushed(id: string): Promise<void> {
|
||||
const db = await getDb();
|
||||
await db.runAsync(
|
||||
"UPDATE incidents SET git_pushed_at = ? WHERE id = ?",
|
||||
[new Date().toISOString(), id]
|
||||
);
|
||||
}
|
||||
|
||||
export async function getMaxTitleNumber(prefix: string): Promise<number> {
|
||||
const db = await getDb();
|
||||
const rows = await db.getAllAsync<{ title: string }>(
|
||||
"SELECT title FROM incidents WHERE title LIKE ?",
|
||||
[`${prefix}%`]
|
||||
);
|
||||
let max = 0;
|
||||
for (const row of rows) {
|
||||
const n = parseInt(row.title.slice(prefix.length), 10);
|
||||
if (!isNaN(n) && n > max) max = n;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
export async function getDistinctServices(): Promise<string[]> {
|
||||
const db = await getDb();
|
||||
const rows = await db.getAllAsync<{ service: string }>(
|
||||
|
||||
+39
@@ -1,3 +1,4 @@
|
||||
import * as SecureStore from "expo-secure-store";
|
||||
import { incidentToFilename, renderMarkdown } from "./markdown";
|
||||
import type { Incident } from "@/types/incident";
|
||||
|
||||
@@ -19,6 +20,44 @@ export interface PushResult {
|
||||
|
||||
const TIMEOUT_MS = 15_000;
|
||||
|
||||
export async function loadGitConfig(): Promise<GitConfig | null> {
|
||||
const [provider, gu, gt, go, gr, ghu, ghown, ghrepo, glu, glt, glo, glr] =
|
||||
await Promise.all([
|
||||
SecureStore.getItemAsync("git_provider"),
|
||||
SecureStore.getItemAsync("gitea_url"),
|
||||
SecureStore.getItemAsync("gitea_token"),
|
||||
SecureStore.getItemAsync("gitea_owner"),
|
||||
SecureStore.getItemAsync("gitea_repo"),
|
||||
SecureStore.getItemAsync("github_token"),
|
||||
SecureStore.getItemAsync("github_owner"),
|
||||
SecureStore.getItemAsync("github_repo"),
|
||||
SecureStore.getItemAsync("gitlab_url"),
|
||||
SecureStore.getItemAsync("gitlab_token"),
|
||||
SecureStore.getItemAsync("gitlab_owner"),
|
||||
SecureStore.getItemAsync("gitlab_repo"),
|
||||
]);
|
||||
|
||||
const p = (provider ?? "gitea") as GitProvider;
|
||||
let url: string | undefined;
|
||||
let token: string | null;
|
||||
let owner: string | null;
|
||||
let repo: string | null;
|
||||
|
||||
if (p === "gitea") {
|
||||
url = gu ?? undefined;
|
||||
token = gt; owner = go; repo = gr;
|
||||
} else if (p === "github") {
|
||||
token = ghu; owner = ghown; repo = ghrepo;
|
||||
} else {
|
||||
url = glu ?? undefined;
|
||||
token = glt; owner = glo; repo = glr;
|
||||
}
|
||||
|
||||
const needsUrl = p === "gitea" || p === "gitlab";
|
||||
if (!token || !owner || !repo || (needsUrl && !url)) return null;
|
||||
return { provider: p, url, token, owner, repo };
|
||||
}
|
||||
|
||||
function fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
||||
|
||||
+2
-1
@@ -11,6 +11,7 @@ export interface Incident {
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
tags: string[];
|
||||
gitPushedAt: string | null;
|
||||
}
|
||||
|
||||
export type IncidentDraft = Omit<Incident, "id" | "createdAt" | "updatedAt">;
|
||||
export type IncidentDraft = Omit<Incident, "id" | "createdAt" | "updatedAt" | "gitPushedAt">;
|
||||
|
||||
Reference in New Issue
Block a user