UUID everywhere: all API calls use ID instead of slug
- Project API: project.id for all CRUD + files + members - Member API: member.id for get/update/regenerate/revoke - ProjectSettings: member selection by id - ChatPanel/MentionInput: projectId prop - AgentModal: agent.id for API, slug for display only
This commit is contained in:
parent
fd3e454dff
commit
52be59dc9f
@ -28,7 +28,7 @@ export default function AgentModal({ agent, onClose, onUpdated }: Props) {
|
||||
setSaving(true);
|
||||
try {
|
||||
const caps = capabilities.split(",").map((c) => c.trim()).filter(Boolean);
|
||||
const updated = await updateMember(agent.slug, {
|
||||
const updated = await updateMember(agent.id, {
|
||||
name: name.trim(),
|
||||
agent_config: {
|
||||
capabilities: caps,
|
||||
@ -49,7 +49,7 @@ export default function AgentModal({ agent, onClose, onUpdated }: Props) {
|
||||
|
||||
const handleRegenerate = async () => {
|
||||
try {
|
||||
const { token } = await regenerateToken(agent.slug);
|
||||
const { token } = await regenerateToken(agent.id);
|
||||
setCurrentToken(token);
|
||||
setConfirmRegen(false);
|
||||
} catch (e) {
|
||||
@ -59,7 +59,7 @@ export default function AgentModal({ agent, onClose, onUpdated }: Props) {
|
||||
|
||||
const handleRevoke = async () => {
|
||||
try {
|
||||
await revokeToken(agent.slug);
|
||||
await revokeToken(agent.id);
|
||||
setCurrentToken(null);
|
||||
setConfirmRevoke(false);
|
||||
} catch (e) {
|
||||
|
||||
@ -22,7 +22,7 @@ interface StreamingState {
|
||||
|
||||
interface Props {
|
||||
chatId: string;
|
||||
projectSlug?: string;
|
||||
projectId?: string;
|
||||
}
|
||||
|
||||
function formatFileSize(bytes: number): string {
|
||||
@ -35,7 +35,7 @@ function isImageMime(mime: string | null): boolean {
|
||||
return !!mime && mime.startsWith("image/");
|
||||
}
|
||||
|
||||
export default function ChatPanel({ chatId, projectSlug }: Props) {
|
||||
export default function ChatPanel({ chatId, projectId }: Props) {
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
const [input, setInput] = useState("");
|
||||
const [sending, setSending] = useState(false);
|
||||
@ -399,7 +399,7 @@ export default function ChatPanel({ chatId, projectSlug }: Props) {
|
||||
{uploading ? "⏳" : "📎"}
|
||||
</button>
|
||||
<MentionInput
|
||||
projectSlug={projectSlug || ""}
|
||||
projectId={projectId || ""}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
onSubmit={handleSend}
|
||||
|
||||
@ -3,7 +3,7 @@ import type { ProjectMember } from "@/lib/api";
|
||||
import { getProjectMembers } from "@/lib/api";
|
||||
|
||||
interface Props {
|
||||
projectSlug: string;
|
||||
projectId: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
onSubmit: () => void;
|
||||
@ -11,7 +11,7 @@ interface Props {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export default function MentionInput({ projectSlug, value, onChange, onSubmit, placeholder, disabled }: Props) {
|
||||
export default function MentionInput({ projectId, value, onChange, onSubmit, placeholder, disabled }: Props) {
|
||||
const [members, setMembers] = useState<ProjectMember[]>([]);
|
||||
const [showDropdown, setShowDropdown] = useState(false);
|
||||
const [filter, setFilter] = useState("");
|
||||
@ -22,9 +22,9 @@ export default function MentionInput({ projectSlug, value, onChange, onSubmit, p
|
||||
|
||||
// Load members once
|
||||
useEffect(() => {
|
||||
if (!projectSlug) return;
|
||||
getProjectMembers(projectSlug).then(setMembers).catch(() => {});
|
||||
}, [projectSlug]);
|
||||
if (!projectId) return;
|
||||
getProjectMembers(projectId).then(setMembers).catch(() => {});
|
||||
}, [projectId]);
|
||||
|
||||
const filtered = members.filter((m) =>
|
||||
m.name.toLowerCase().includes(filter.toLowerCase()) ||
|
||||
|
||||
@ -47,14 +47,14 @@ export default function ProjectFiles({ project }: Props) {
|
||||
|
||||
const loadFiles = useCallback(async () => {
|
||||
try {
|
||||
const data = await getProjectFiles(project.slug, search || undefined);
|
||||
const data = await getProjectFiles(project.id, search || undefined);
|
||||
setFiles(data);
|
||||
} catch (e) {
|
||||
console.error("Failed to load files:", e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [project.slug, search]);
|
||||
}, [project.id, search]);
|
||||
|
||||
// Debounced search
|
||||
useEffect(() => {
|
||||
@ -67,7 +67,7 @@ export default function ProjectFiles({ project }: Props) {
|
||||
setUploading(true);
|
||||
try {
|
||||
for (const file of Array.from(fileList)) {
|
||||
await uploadProjectFile(project.slug, file, uploadDesc || undefined);
|
||||
await uploadProjectFile(project.id, file, uploadDesc || undefined);
|
||||
}
|
||||
setUploadDesc("");
|
||||
setShowUploadDesc(false);
|
||||
@ -83,7 +83,7 @@ export default function ProjectFiles({ project }: Props) {
|
||||
const handleDelete = async (f: ProjectFile) => {
|
||||
if (!confirm(`Удалить ${f.filename}?`)) return;
|
||||
try {
|
||||
await deleteProjectFile(project.slug, f.id);
|
||||
await deleteProjectFile(project.id, f.id);
|
||||
setFiles((prev) => prev.filter((x) => x.id !== f.id));
|
||||
} catch (e) {
|
||||
console.error("Delete failed:", e);
|
||||
@ -92,7 +92,7 @@ export default function ProjectFiles({ project }: Props) {
|
||||
|
||||
const handleSaveDesc = async (f: ProjectFile) => {
|
||||
try {
|
||||
const updated = await updateProjectFile(project.slug, f.id, { description: editDesc });
|
||||
const updated = await updateProjectFile(project.id, f.id, { description: editDesc });
|
||||
setFiles((prev) => prev.map((x) => (x.id === f.id ? updated : x)));
|
||||
setEditingId(null);
|
||||
} catch (e) {
|
||||
@ -171,7 +171,7 @@ export default function ProjectFiles({ project }: Props) {
|
||||
{!loading && files.length > 0 && (
|
||||
<div className="space-y-1">
|
||||
{files.map((f) => {
|
||||
const url = getProjectFileUrl(project.slug, f.id);
|
||||
const url = getProjectFileUrl(project.id, f.id);
|
||||
const isImage = f.mime_type?.startsWith("image/");
|
||||
return (
|
||||
<div
|
||||
|
||||
@ -36,7 +36,7 @@ export default function ProjectSettings({ project, onUpdated }: Props) {
|
||||
const loadMembers = async () => {
|
||||
try {
|
||||
const [projectMembers, allMembersList] = await Promise.all([
|
||||
getProjectMembers(project.slug),
|
||||
getProjectMembers(project.id),
|
||||
getMembers()
|
||||
]);
|
||||
setMembers(projectMembers);
|
||||
@ -58,8 +58,8 @@ export default function ProjectSettings({ project, onUpdated }: Props) {
|
||||
const handleAddMember = async () => {
|
||||
if (!selectedMember) return;
|
||||
try {
|
||||
await addProjectMember(project.slug, selectedMember);
|
||||
const newMember = allMembers.find((m) => m.slug === selectedMember);
|
||||
await addProjectMember(project.id, selectedMember);
|
||||
const newMember = allMembers.find((m) => m.id === selectedMember);
|
||||
if (newMember) {
|
||||
setMembers([...members, {
|
||||
id: newMember.id,
|
||||
@ -75,10 +75,10 @@ export default function ProjectSettings({ project, onUpdated }: Props) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveMember = async (memberSlug: string) => {
|
||||
const handleRemoveMember = async (memberId: string) => {
|
||||
try {
|
||||
await removeProjectMember(project.slug, memberSlug);
|
||||
setMembers(members.filter((m) => m.slug !== memberSlug));
|
||||
await removeProjectMember(project.id, memberId);
|
||||
setMembers(members.filter((m) => m.id !== memberId));
|
||||
} catch (e) {
|
||||
console.error("Failed to remove member:", e);
|
||||
}
|
||||
@ -88,7 +88,7 @@ export default function ProjectSettings({ project, onUpdated }: Props) {
|
||||
setSaving(true);
|
||||
try {
|
||||
const urls = repoUrls.split("\n").map((u) => u.trim()).filter(Boolean);
|
||||
const updated = await updateProject(project.slug, {
|
||||
const updated = await updateProject(project.id, {
|
||||
name: name.trim(),
|
||||
description: description.trim() || null,
|
||||
repo_urls: urls,
|
||||
@ -106,7 +106,7 @@ export default function ProjectSettings({ project, onUpdated }: Props) {
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await deleteProject(project.slug);
|
||||
await deleteProject(project.id);
|
||||
navigate("/");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@ -197,7 +197,7 @@ export default function ProjectSettings({ project, onUpdated }: Props) {
|
||||
</div>
|
||||
{member.role !== "owner" && (
|
||||
<button
|
||||
onClick={() => handleRemoveMember(member.slug)}
|
||||
onClick={() => handleRemoveMember(member.id)}
|
||||
className="px-2 py-1 text-xs text-red-400 hover:bg-red-500/10 rounded"
|
||||
>
|
||||
Убрать
|
||||
@ -217,7 +217,7 @@ export default function ProjectSettings({ project, onUpdated }: Props) {
|
||||
>
|
||||
<option value="">Добавить участника...</option>
|
||||
{availableMembers.map((member) => (
|
||||
<option key={member.slug} value={member.slug}>
|
||||
<option key={member.id} value={member.id}>
|
||||
{member.name} ({member.slug}) - {member.type}
|
||||
</option>
|
||||
))}
|
||||
|
||||
@ -169,20 +169,20 @@ export async function getProjects(): Promise<Project[]> {
|
||||
return request("/api/v1/projects");
|
||||
}
|
||||
|
||||
export async function getProject(slug: string): Promise<Project> {
|
||||
return request(`/api/v1/projects/${slug}`);
|
||||
export async function getProject(projectId: string): Promise<Project> {
|
||||
return request(`/api/v1/projects/${projectId}`);
|
||||
}
|
||||
|
||||
export async function createProject(data: { name: string; slug: string; description?: string }): Promise<Project> {
|
||||
return request("/api/v1/projects", { method: "POST", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function updateProject(slug: string, data: Partial<Pick<Project, "name" | "description" | "repo_urls" | "status">>): Promise<Project> {
|
||||
return request(`/api/v1/projects/${slug}`, { method: "PATCH", body: JSON.stringify(data) });
|
||||
export async function updateProject(projectId: string, data: Partial<Pick<Project, "name" | "slug" | "description" | "repo_urls" | "status">>): Promise<Project> {
|
||||
return request(`/api/v1/projects/${projectId}`, { method: "PATCH", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function deleteProject(slug: string): Promise<void> {
|
||||
await request(`/api/v1/projects/${slug}`, { method: "DELETE" });
|
||||
export async function deleteProject(projectId: string): Promise<void> {
|
||||
await request(`/api/v1/projects/${projectId}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
// --- Tasks ---
|
||||
@ -287,8 +287,8 @@ export async function getMembers(): Promise<Member[]> {
|
||||
return request("/api/v1/members");
|
||||
}
|
||||
|
||||
export async function getMember(slug: string): Promise<Member> {
|
||||
return request(`/api/v1/members/${slug}`);
|
||||
export async function getMember(memberId: string): Promise<Member> {
|
||||
return request(`/api/v1/members/${memberId}`);
|
||||
}
|
||||
|
||||
export async function createMember(data: {
|
||||
@ -300,21 +300,21 @@ export async function createMember(data: {
|
||||
return request("/api/v1/members", { method: "POST", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function updateMember(slug: string, data: {
|
||||
export async function updateMember(memberId: string, data: {
|
||||
name?: string;
|
||||
role?: string;
|
||||
status?: string;
|
||||
agent_config?: Partial<AgentConfig>;
|
||||
}): Promise<Member> {
|
||||
return request(`/api/v1/members/${slug}`, { method: "PATCH", body: JSON.stringify(data) });
|
||||
return request(`/api/v1/members/${memberId}`, { method: "PATCH", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function regenerateToken(slug: string): Promise<{ token: string }> {
|
||||
return request(`/api/v1/members/${slug}/regenerate-token`, { method: "POST" });
|
||||
export async function regenerateToken(memberId: string): Promise<{ token: string }> {
|
||||
return request(`/api/v1/members/${memberId}/regenerate-token`, { method: "POST" });
|
||||
}
|
||||
|
||||
export async function revokeToken(slug: string): Promise<void> {
|
||||
await request(`/api/v1/members/${slug}/revoke-token`, { method: "POST" });
|
||||
export async function revokeToken(memberId: string): Promise<void> {
|
||||
await request(`/api/v1/members/${memberId}/revoke-token`, { method: "POST" });
|
||||
}
|
||||
|
||||
// --- Project Files ---
|
||||
@ -330,18 +330,18 @@ export interface ProjectFile {
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export async function getProjectFiles(slug: string, search?: string): Promise<ProjectFile[]> {
|
||||
export async function getProjectFiles(projectId: string, search?: string): Promise<ProjectFile[]> {
|
||||
const qs = search ? `?search=${encodeURIComponent(search)}` : "";
|
||||
return request(`/api/v1/projects/${slug}/files${qs}`);
|
||||
return request(`/api/v1/projects/${projectId}/files${qs}`);
|
||||
}
|
||||
|
||||
export async function uploadProjectFile(slug: string, file: File, description?: string): Promise<ProjectFile> {
|
||||
export async function uploadProjectFile(projectId: string, file: File, description?: string): Promise<ProjectFile> {
|
||||
const token = getToken();
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
if (description) formData.append("description", description);
|
||||
|
||||
const res = await fetch(`${API_BASE}/api/v1/projects/${slug}/files`, {
|
||||
const res = await fetch(`${API_BASE}/api/v1/projects/${projectId}/files`, {
|
||||
method: "POST",
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
body: formData,
|
||||
@ -358,18 +358,18 @@ export async function uploadProjectFile(slug: string, file: File, description?:
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export function getProjectFileUrl(slug: string, fileId: string): string {
|
||||
export function getProjectFileUrl(projectId: string, fileId: string): string {
|
||||
const token = getToken();
|
||||
const qs = token ? `?token=${encodeURIComponent(token)}` : "";
|
||||
return `${API_BASE}/api/v1/projects/${slug}/files/${fileId}/download${qs}`;
|
||||
return `${API_BASE}/api/v1/projects/${projectId}/files/${fileId}/download${qs}`;
|
||||
}
|
||||
|
||||
export async function updateProjectFile(slug: string, fileId: string, data: { description?: string }): Promise<ProjectFile> {
|
||||
return request(`/api/v1/projects/${slug}/files/${fileId}`, { method: "PATCH", body: JSON.stringify(data) });
|
||||
export async function updateProjectFile(projectId: string, fileId: string, data: { description?: string }): Promise<ProjectFile> {
|
||||
return request(`/api/v1/projects/${projectId}/files/${fileId}`, { method: "PATCH", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function deleteProjectFile(slug: string, fileId: string): Promise<void> {
|
||||
await request(`/api/v1/projects/${slug}/files/${fileId}`, { method: "DELETE" });
|
||||
export async function deleteProjectFile(projectId: string, fileId: string): Promise<void> {
|
||||
await request(`/api/v1/projects/${projectId}/files/${fileId}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
// --- Chat File Upload ---
|
||||
@ -412,17 +412,17 @@ export function getAttachmentUrl(attachmentId: string): string {
|
||||
|
||||
// --- Project Members ---
|
||||
|
||||
export async function getProjectMembers(slug: string): Promise<ProjectMember[]> {
|
||||
return request(`/api/v1/projects/${slug}/members`);
|
||||
export async function getProjectMembers(projectId: string): Promise<ProjectMember[]> {
|
||||
return request(`/api/v1/projects/${projectId}/members`);
|
||||
}
|
||||
|
||||
export async function addProjectMember(slug: string, memberSlug: string): Promise<{ ok: boolean }> {
|
||||
return request(`/api/v1/projects/${slug}/members`, {
|
||||
export async function addProjectMember(projectId: string, memberId: string): Promise<{ ok: boolean }> {
|
||||
return request(`/api/v1/projects/${projectId}/members`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ slug: memberSlug })
|
||||
body: JSON.stringify({ member_id: memberId })
|
||||
});
|
||||
}
|
||||
|
||||
export async function removeProjectMember(slug: string, memberSlug: string): Promise<{ ok: boolean }> {
|
||||
return request(`/api/v1/projects/${slug}/members/${memberSlug}`, { method: "DELETE" });
|
||||
export async function removeProjectMember(projectId: string, memberId: string): Promise<{ ok: boolean }> {
|
||||
return request(`/api/v1/projects/${projectId}/members/${memberId}`, { method: "DELETE" });
|
||||
}
|
||||
@ -101,7 +101,7 @@ export default function ProjectPage() {
|
||||
<KanbanBoard projectId={project.id} projectSlug={project.slug} />
|
||||
)}
|
||||
{activeTab === "chat" && project.chat_id && (
|
||||
<ChatPanel chatId={project.chat_id} projectSlug={project.slug} />
|
||||
<ChatPanel chatId={project.chat_id} projectId={project.id} />
|
||||
)}
|
||||
{activeTab === "chat" && !project.chat_id && (
|
||||
<div className="flex items-center justify-center h-full text-[var(--muted)] text-sm">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user