2026-05-02 03:18:47 +02:00
|
|
|
import api from "./client";
|
|
|
|
|
import type {
|
|
|
|
|
Project,
|
|
|
|
|
ProjectCreate,
|
|
|
|
|
ProjectUpdate,
|
|
|
|
|
ProjectSummary,
|
|
|
|
|
Team,
|
|
|
|
|
} from "@/types";
|
|
|
|
|
import { isMockMode } from "@/lib/mock-data";
|
|
|
|
|
|
|
|
|
|
// Mock data for offline mode
|
|
|
|
|
const mockProjects: Project[] = [];
|
|
|
|
|
|
|
|
|
|
export interface ProjectFilters {
|
|
|
|
|
assigned_cell?: Team;
|
|
|
|
|
active_only?: boolean;
|
|
|
|
|
limit?: number;
|
|
|
|
|
offset?: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const projectsApi = {
|
|
|
|
|
// List projects with optional filters
|
|
|
|
|
list: async (filters?: ProjectFilters): Promise<ProjectSummary[]> => {
|
|
|
|
|
if (isMockMode()) {
|
|
|
|
|
let projects = [...mockProjects];
|
|
|
|
|
if (filters?.assigned_cell) {
|
2026-06-29 05:38:21 +02:00
|
|
|
projects = projects.filter(
|
|
|
|
|
(p) => p.assigned_cell === filters.assigned_cell,
|
|
|
|
|
);
|
2026-05-02 03:18:47 +02:00
|
|
|
}
|
|
|
|
|
if (filters?.active_only) {
|
|
|
|
|
projects = projects.filter((p) => p.is_active);
|
|
|
|
|
}
|
|
|
|
|
return projects.map((p) => ({
|
|
|
|
|
id: p.id,
|
|
|
|
|
name: p.name,
|
|
|
|
|
slug: p.slug,
|
|
|
|
|
git_url: p.git_url,
|
|
|
|
|
assigned_cell: p.assigned_cell,
|
|
|
|
|
is_active: p.is_active,
|
|
|
|
|
has_workspace: !!p.workspace_path,
|
2026-06-29 05:38:21 +02:00
|
|
|
has_git_token: false, // Mock mode has no tokens
|
2026-05-02 03:18:47 +02:00
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const params = new URLSearchParams();
|
2026-06-29 05:38:21 +02:00
|
|
|
if (filters?.assigned_cell)
|
|
|
|
|
params.append("assigned_cell", filters.assigned_cell);
|
|
|
|
|
if (filters?.active_only !== undefined)
|
|
|
|
|
params.append("active_only", String(filters.active_only));
|
2026-05-02 03:18:47 +02:00
|
|
|
if (filters?.limit) params.append("limit", String(filters.limit));
|
|
|
|
|
if (filters?.offset) params.append("offset", String(filters.offset));
|
|
|
|
|
|
|
|
|
|
const url = "/projects?" + params.toString();
|
|
|
|
|
const { data } = await api.get<ProjectSummary[]>(url);
|
|
|
|
|
return data;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Get single project
|
|
|
|
|
get: async (projectId: string): Promise<Project> => {
|
|
|
|
|
if (isMockMode()) {
|
|
|
|
|
const project = mockProjects.find((p) => p.id === projectId);
|
|
|
|
|
if (!project) throw new Error("Project not found");
|
|
|
|
|
return project;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { data } = await api.get<Project>("/projects/" + projectId);
|
|
|
|
|
return data;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Create project (PM only)
|
|
|
|
|
create: async (project: ProjectCreate): Promise<Project> => {
|
|
|
|
|
if (isMockMode()) {
|
|
|
|
|
const now = new Date().toISOString();
|
|
|
|
|
const newProject: Project = {
|
|
|
|
|
id: `project-${Date.now()}`,
|
|
|
|
|
name: project.name,
|
|
|
|
|
slug: project.slug,
|
|
|
|
|
git_url: project.git_url,
|
|
|
|
|
default_branch: project.default_branch ?? "main",
|
|
|
|
|
protected_branches: project.protected_branches ?? ["main", "master"],
|
|
|
|
|
assigned_cell: project.assigned_cell,
|
2026-06-29 05:38:21 +02:00
|
|
|
has_git_token: !!project.git_token, // Mock token status
|
2026-05-02 03:18:47 +02:00
|
|
|
is_active: true,
|
|
|
|
|
test_command: project.test_command ?? null,
|
|
|
|
|
lint_command: project.lint_command ?? null,
|
|
|
|
|
format_command: project.format_command ?? null,
|
|
|
|
|
typecheck_command: project.typecheck_command ?? null,
|
|
|
|
|
build_command: project.build_command ?? null,
|
2026-06-14 05:24:09 +02:00
|
|
|
quality_command: project.quality_command ?? null,
|
2026-06-25 21:11:36 +02:00
|
|
|
ci_watch_enabled: false,
|
|
|
|
|
ci_watch_workflow: null,
|
2026-07-07 10:09:23 +02:00
|
|
|
video_engine_enabled: false,
|
2026-06-25 21:11:36 +02:00
|
|
|
dep_update_command: null,
|
|
|
|
|
dep_update_paths: null,
|
2026-07-03 19:24:00 +02:00
|
|
|
sandbox_services: null,
|
2026-05-02 03:18:47 +02:00
|
|
|
workspace_path: null,
|
|
|
|
|
last_synced_at: null,
|
|
|
|
|
head_commit: null,
|
|
|
|
|
created_by: "mock-user",
|
|
|
|
|
created_at: now,
|
|
|
|
|
updated_at: null,
|
|
|
|
|
};
|
|
|
|
|
mockProjects.push(newProject);
|
|
|
|
|
return newProject;
|
|
|
|
|
}
|
|
|
|
|
const { data } = await api.post<Project>("/projects", project);
|
|
|
|
|
return data;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Update project (PM only)
|
2026-06-29 05:38:21 +02:00
|
|
|
update: async (
|
|
|
|
|
projectId: string,
|
|
|
|
|
updates: ProjectUpdate,
|
|
|
|
|
): Promise<Project> => {
|
2026-05-02 03:18:47 +02:00
|
|
|
if (isMockMode()) {
|
|
|
|
|
const idx = mockProjects.findIndex((p) => p.id === projectId);
|
|
|
|
|
if (idx === -1) throw new Error("Project not found");
|
|
|
|
|
const now = new Date().toISOString();
|
|
|
|
|
mockProjects[idx] = { ...mockProjects[idx], ...updates, updated_at: now };
|
|
|
|
|
return mockProjects[idx];
|
|
|
|
|
}
|
2026-06-29 05:38:21 +02:00
|
|
|
const { data } = await api.patch<Project>(
|
|
|
|
|
"/projects/" + projectId,
|
|
|
|
|
updates,
|
|
|
|
|
);
|
2026-05-02 03:18:47 +02:00
|
|
|
return data;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Set workspace path for local development
|
2026-06-29 05:38:21 +02:00
|
|
|
setWorkspace: async (
|
|
|
|
|
projectId: string,
|
|
|
|
|
workspacePath: string,
|
|
|
|
|
): Promise<Project> => {
|
2026-05-02 03:18:47 +02:00
|
|
|
if (isMockMode()) {
|
|
|
|
|
const idx = mockProjects.findIndex((p) => p.id === projectId);
|
|
|
|
|
if (idx === -1) throw new Error("Project not found");
|
|
|
|
|
const now = new Date().toISOString();
|
2026-06-29 05:38:21 +02:00
|
|
|
mockProjects[idx] = {
|
|
|
|
|
...mockProjects[idx],
|
|
|
|
|
workspace_path: workspacePath,
|
|
|
|
|
updated_at: now,
|
|
|
|
|
};
|
2026-05-02 03:18:47 +02:00
|
|
|
return mockProjects[idx];
|
|
|
|
|
}
|
2026-06-29 05:38:21 +02:00
|
|
|
const { data } = await api.post<Project>(
|
|
|
|
|
"/projects/" + projectId + "/workspace",
|
|
|
|
|
{ workspace_path: workspacePath },
|
|
|
|
|
);
|
2026-05-02 03:18:47 +02:00
|
|
|
return data;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Update sync state (for tracking git status)
|
|
|
|
|
updateSyncState: async (
|
|
|
|
|
projectId: string,
|
2026-06-29 05:38:21 +02:00
|
|
|
headCommit: string,
|
2026-05-02 03:18:47 +02:00
|
|
|
): Promise<Project> => {
|
|
|
|
|
if (isMockMode()) {
|
|
|
|
|
const idx = mockProjects.findIndex((p) => p.id === projectId);
|
|
|
|
|
if (idx === -1) throw new Error("Project not found");
|
|
|
|
|
const now = new Date().toISOString();
|
|
|
|
|
mockProjects[idx] = {
|
|
|
|
|
...mockProjects[idx],
|
|
|
|
|
head_commit: headCommit,
|
|
|
|
|
last_synced_at: now,
|
|
|
|
|
updated_at: now,
|
|
|
|
|
};
|
|
|
|
|
return mockProjects[idx];
|
|
|
|
|
}
|
2026-06-29 05:38:21 +02:00
|
|
|
const { data } = await api.post<Project>(
|
|
|
|
|
"/projects/" + projectId + "/sync-state",
|
|
|
|
|
{
|
|
|
|
|
head_commit: headCommit,
|
|
|
|
|
},
|
|
|
|
|
);
|
2026-05-02 03:18:47 +02:00
|
|
|
return data;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Deactivate project (soft delete)
|
|
|
|
|
deactivate: async (projectId: string): Promise<Project> => {
|
|
|
|
|
if (isMockMode()) {
|
|
|
|
|
const idx = mockProjects.findIndex((p) => p.id === projectId);
|
|
|
|
|
if (idx === -1) throw new Error("Project not found");
|
|
|
|
|
const now = new Date().toISOString();
|
2026-06-29 05:38:21 +02:00
|
|
|
mockProjects[idx] = {
|
|
|
|
|
...mockProjects[idx],
|
|
|
|
|
is_active: false,
|
|
|
|
|
updated_at: now,
|
|
|
|
|
};
|
2026-05-02 03:18:47 +02:00
|
|
|
return mockProjects[idx];
|
|
|
|
|
}
|
2026-06-29 05:38:21 +02:00
|
|
|
const { data } = await api.patch<Project>("/projects/" + projectId, {
|
|
|
|
|
is_active: false,
|
|
|
|
|
});
|
2026-05-02 03:18:47 +02:00
|
|
|
return data;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Delete project permanently
|
|
|
|
|
delete: async (projectId: string): Promise<void> => {
|
|
|
|
|
if (isMockMode()) {
|
|
|
|
|
const idx = mockProjects.findIndex((p) => p.id === projectId);
|
|
|
|
|
if (idx !== -1) mockProjects.splice(idx, 1);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
await api.delete("/projects/" + projectId);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Grant agent access to project
|
|
|
|
|
grantAccess: async (projectId: string, agentId: string): Promise<void> => {
|
|
|
|
|
if (isMockMode()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
await api.post("/projects/" + projectId + "/access/" + agentId);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Revoke agent access from project
|
|
|
|
|
revokeAccess: async (projectId: string, agentId: string): Promise<void> => {
|
|
|
|
|
if (isMockMode()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
await api.delete("/projects/" + projectId + "/access/" + agentId);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Trigger git sync for project
|
|
|
|
|
sync: async (projectId: string): Promise<Project> => {
|
|
|
|
|
if (isMockMode()) {
|
|
|
|
|
const idx = mockProjects.findIndex((p) => p.id === projectId);
|
|
|
|
|
if (idx === -1) throw new Error("Project not found");
|
|
|
|
|
const now = new Date().toISOString();
|
|
|
|
|
mockProjects[idx] = {
|
|
|
|
|
...mockProjects[idx],
|
|
|
|
|
last_synced_at: now,
|
|
|
|
|
updated_at: now,
|
|
|
|
|
};
|
|
|
|
|
return mockProjects[idx];
|
|
|
|
|
}
|
2026-06-29 05:38:21 +02:00
|
|
|
const { data } = await api.post<Project>(
|
|
|
|
|
"/projects/" + projectId + "/sync",
|
|
|
|
|
);
|
2026-05-02 03:18:47 +02:00
|
|
|
return data;
|
|
|
|
|
},
|
|
|
|
|
};
|