File size: 3,937 Bytes
71638d4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | import { API_BASE_URL } from "../config/supabase";
import { Issue, IssueListResponse, LocationData } from "../types";
import EventSource, { EventSourceListener } from "react-native-sse";
import { cacheService } from "./cacheService";
class IssueService {
private baseUrl: string;
constructor() {
this.baseUrl = API_BASE_URL;
}
async createIssue(
imageUri: string,
location: LocationData,
description?: string,
accessToken?: string,
): Promise<{ issue_id: string; stream_url: string }> {
const formData = new FormData();
const imageFile = {
uri: imageUri,
type: "image/jpeg",
name: "issue_photo.jpg",
} as any;
formData.append("images", imageFile);
formData.append("latitude", location.latitude.toString());
formData.append("longitude", location.longitude.toString());
formData.append("accuracy_meters", location.accuracy.toString());
formData.append("platform", "mobile_app");
formData.append("device_model", "expo_device");
if (description) {
formData.append("description", description);
}
if (accessToken) {
formData.append("authorization", `Bearer ${accessToken}`);
console.log("[IssueService] Authorization token appended to form data");
} else {
console.warn("[IssueService] No access token provided - issue will be created without user_id");
}
const headers: Record<string, string> = {
"Content-Type": "multipart/form-data",
};
const response = await fetch(`${this.baseUrl}/issues/stream`, {
method: "POST",
headers,
body: formData,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || "Failed to create issue");
}
await cacheService.clearCache();
return response.json();
}
async getIssue(issueId: string): Promise<Issue> {
const response = await fetch(`${this.baseUrl}/issues/${issueId}`);
if (!response.ok) {
throw new Error("Failed to fetch issue");
}
return response.json();
}
async listIssues(
page: number = 1,
pageSize: number = 20,
state?: string,
userId?: string,
): Promise<IssueListResponse> {
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
});
if (state) {
params.append("state", state);
}
if (userId) {
params.append("user_id", userId);
}
const response = await fetch(`${this.baseUrl}/issues?${params}`);
if (!response.ok) {
throw new Error("Failed to fetch issues");
}
return response.json();
}
async connectToFlowStream(
issueId: string,
onMessage: (data: any) => void,
onError: (error: Error) => void,
): Promise<() => void> {
const eventSource = new EventSource(`${this.baseUrl}/flow/flow/${issueId}`);
const listener: EventSourceListener = (event) => {
if (event.type === "message") {
try {
const data = JSON.parse(event.data || "{}");
onMessage(data);
} catch (e) {
console.error("Failed to parse SSE message:", e);
}
} else if (event.type === "error") {
onError(new Error("SSE connection error"));
}
};
eventSource.addEventListener("message", listener);
eventSource.addEventListener("error", listener);
return () => {
eventSource.removeAllEventListeners();
eventSource.close();
};
}
async confirmIssue(issueId: string, confirmed: boolean): Promise<Issue> {
const response = await fetch(`${this.baseUrl}/issues/${issueId}/confirm`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ confirmed }),
});
if (!response.ok) {
throw new Error("Failed to confirm issue");
}
return response.json();
}
}
export const issueService = new IssueService();
|