* Fork an existing project to create a copy for the current user * @param userId The user ID making the request * @param projectId The project ID to fork * @returns The chat associated with the newly created project
(userId: string, projectId: string)
| 541 | |
| 542 | try { |
| 543 | const sandbox = await sandboxHandle(newProjectId); |
| 544 | const remoteZip = '/tmp/codefox-fork.zip'; |
| 545 | await sandbox.writeFiles([ |
| 546 | { path: remoteZip, content: new Uint8Array(fs.readFileSync(zipPath)) }, |
| 547 | ]); |
| 548 | const unpack = await sandbox.runCommand({ |
| 549 | cmd: 'python3', |
| 550 | args: ['-m', 'zipfile', '-e', remoteZip, SANDBOX_ROOT], |
| 551 | timeoutMs: 5 * 60 * 1000, |
| 552 | }); |
| 553 | if (unpack.exitCode !== 0) { |
| 554 | throw new Error( |
| 555 | `Unpacking the fork failed: ${(await unpack.stderr()).slice(-300)}`, |
| 556 | ); |
| 557 | } |
| 558 | return newProjectId; |
| 559 | } finally { |
| 560 | fs.promises |
| 561 | .unlink(zipPath) |
| 562 | .catch((error) => |
| 563 | this.logger.warn(`Could not remove ${zipPath}: ${error}`), |
| 564 | ); |
| 565 | } |
| 566 | } |
| 567 | |
| 568 | /** |
| 569 | * Your own project, copied. Same machinery as a fork — it already copies |
| 570 | * files, mints a new share id and starts private — minus the "not yours" |
| 571 | * rule, which is the only thing that ever stopped you. |
| 572 | * |
| 573 | * ponytail: a flag on forkProject, not a second copy of it. The two differ |
| 574 | * by one guard and one label. |
| 575 | */ |
| 576 | async duplicateProject(userId: string, projectId: string): Promise<Chat> { |
| 577 | return this.forkProject(userId, projectId, true); |
| 578 | } |
| 579 | |
| 580 | async forkProject( |
| 581 | userId: string, |
| 582 | projectId: string, |
| 583 | mine = false, |
| 584 | ): Promise<Chat> { |
| 585 | try { |
| 586 | this.logger.debug(`User ${userId} forking project ${projectId}`); |
| 587 | |
| 588 | // Get the source project |
| 589 | const sourceProject = await this.getProjectById(projectId); |
| 590 | |
| 591 | // Check if the project is public or owned by the requesting user |
| 592 | if (!sourceProject.isPublic && sourceProject.userId !== userId) { |
| 593 | throw new ForbiddenException( |
| 594 | 'Cannot fork a private project you do not own', |
| 595 | ); |
| 596 | } |
| 597 | |
| 598 | // Forking someone else's is the public action; copying your own is |
| 599 | // `duplicateProject`, which sets `mine`. Without the distinction the |
| 600 | // fork button on the wall would offer a no-op on your own card. |
nothing calls this directly
no test coverage detected