Skip to content

Commit 31a9137

Browse files
committed
hub/delete-project: expand functionality and acutally delete files
1 parent d8f481c commit 31a9137

File tree

5 files changed

+62
-22
lines changed

5 files changed

+62
-22
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3+
* License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
4+
*/
5+
6+
// This is used to find files on the share server (public_paths) in "next"
7+
// and also in the hub, for deleting shared files of projects
8+
9+
import { join } from "node:path";
10+
11+
import { projects } from "@cocalc/backend/data";
12+
13+
// Given a project_id/path, return the directory on the file system where
14+
// that path should be located.
15+
export function pathToFiles(project_id: string, path: string): string {
16+
return join(projects.replace("[project_id]", project_id), path);
17+
}

src/packages/database/postgres/delete-projects.ts

+30-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
Code related to permanently deleting projects.
88
*/
99

10+
import { promises as fs } from "node:fs";
11+
import { join } from "node:path";
12+
13+
import { pathToFiles } from "@cocalc/backend/files/path-to-files";
1014
import getLogger from "@cocalc/backend/logger";
1115
import { newCounter } from "@cocalc/backend/metrics";
1216
import getPool from "@cocalc/database/pool";
@@ -105,7 +109,7 @@ SELECT project_id
105109
FROM projects
106110
WHERE deleted = true
107111
AND users IS NULL
108-
AND state ->> 'state' != 'deleted'
112+
AND coalesce(state ->> 'state', '') != 'deleted'
109113
ORDER BY created ASC
110114
LIMIT 1000
111115
`;
@@ -169,10 +173,12 @@ export async function cleanup_old_projects_data(
169173

170174
if (on_prem) {
171175
L2(`delete all project files`);
172-
// TODO: this only works on-prem, and requires the project files to be mounted
176+
await deleteProjectFiles(L2, project_id);
173177

174178
L2(`deleting all shared files`);
175-
// TODO: do it directly like above, and also get rid of all those shares in the database
179+
// this is something like /shared/projects/${project_id}
180+
const shared_path = pathToFiles(project_id, "");
181+
await fs.rm(shared_path, { recursive: true, force: true });
176182

177183
// for now, on-prem only as well. This gets rid of all sorts of data in tables specific to the given project.
178184
delRows += await delete_associated_project_data(L2, project_id);
@@ -261,3 +267,24 @@ async function delete_associated_project_data(
261267

262268
return total;
263269
}
270+
271+
async function deleteProjectFiles(L2, project_id: string) {
272+
// TODO: this only works on-prem, and requires the project files to be mounted
273+
const projects_root = process.env["MOUNTED_PROJECTS_ROOT"];
274+
if (!projects_root) return;
275+
const project_dir = join(projects_root, project_id);
276+
try {
277+
await fs.access(project_dir, fs.constants.F_OK | fs.constants.R_OK);
278+
const stats = await fs.lstat(project_dir);
279+
if (stats.isDirectory()) {
280+
L2(`deleting all files in ${project_dir}`);
281+
await fs.rm(project_dir, { recursive: true, force: true });
282+
} else {
283+
L2(`is not a directory: ${project_dir}`);
284+
}
285+
} catch (err) {
286+
L2(
287+
`not deleting project files: either it does not exist or is not accessible`,
288+
);
289+
}
290+
}

src/packages/next/lib/share/get-contents.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,24 @@
33
* License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
44
*/
55

6-
import pathToFiles from "./path-to-files";
76
import { promises as fs } from "fs";
8-
import { join } from "path";
97
import { sortBy } from "lodash";
8+
import { join } from "path";
9+
10+
import { pathToFiles } from "@cocalc/backend/files/path-to-files";
1011
import { hasSpecialViewer } from "@cocalc/frontend/file-extensions";
1112
import { getExtension } from "./util";
1213

1314
const MB: number = 1000000;
15+
1416
const LIMITS = {
1517
listing: 10000, // directory listing is truncated after this many files
1618
ipynb: 15 * MB,
1719
sagews: 10 * MB,
1820
whiteboard: 5 * MB,
1921
slides: 5 * MB,
2022
other: 2 * MB,
21-
};
23+
} as const;
2224

2325
export interface FileInfo {
2426
name: string;
@@ -40,7 +42,7 @@ export interface PathContents {
4042

4143
export default async function getContents(
4244
project_id: string,
43-
path: string
45+
path: string,
4446
): Promise<PathContents> {
4547
const fsPath = pathToFiles(project_id, path);
4648
const obj: PathContents = {};
@@ -72,7 +74,7 @@ export default async function getContents(
7274
}
7375

7476
async function getDirectoryListing(
75-
path: string
77+
path: string,
7678
): Promise<{ listing: FileInfo[]; truncated?: string }> {
7779
const listing: FileInfo[] = [];
7880
let truncated: string | undefined = undefined;

src/packages/next/lib/share/path-to-files.ts

+3-10
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,17 @@
33
* License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
44
*/
55

6-
import { join } from "path";
6+
import { pathToFiles } from "@cocalc/backend/files/path-to-files";
77
import getPool from "@cocalc/database/pool";
8-
import { projects } from "@cocalc/backend/data";
9-
10-
// Given a project_id/path, return the directory on the file system where
11-
// that path should be located.
12-
export default function pathToFiles(project_id: string, path: string): string {
13-
return join(projects.replace("[project_id]", project_id), path);
14-
}
158

169
export async function pathFromID(
17-
id: string
10+
id: string,
1811
): Promise<{ projectPath: string; fsPath: string }> {
1912
// 'infinite' since actually result can't change since id determines the path (it's a reverse sha1 hash computation)
2013
const pool = getPool("infinite");
2114
const { rows } = await pool.query(
2215
"SELECT project_id, path FROM public_paths WHERE id=$1 AND disabled IS NOT TRUE",
23-
[id]
16+
[id],
2417
);
2518
if (rows.length == 0) {
2619
throw Error(`no such public path: ${id}`);

src/packages/next/lib/share/virtual-hosts.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ Support for virtual hosts.
88
*/
99

1010
import type { Request, Response } from "express";
11+
12+
import basePath from "@cocalc/backend/base-path";
13+
import { pathToFiles } from "@cocalc/backend/files/path-to-files";
1114
import { getLogger } from "@cocalc/backend/logger";
12-
import pathToFiles from "./path-to-files";
1315
import isAuthenticated from "./authenticate";
1416
import getVirtualHostInfo from "./get-vhost-info";
1517
import { staticHandler } from "./handle-raw";
16-
import basePath from "@cocalc/backend/base-path";
1718

1819
const logger = getLogger("virtual-hosts");
1920

@@ -23,7 +24,7 @@ export default function virtualHostsMiddleware() {
2324
return async function (
2425
req: Request,
2526
res: Response,
26-
next: Function
27+
next: Function,
2728
): Promise<void> {
2829
// For debugging in cc-in-cc dev, just manually set host to something
2930
// else and comment this out. That's the only way, since dev is otherwise
@@ -69,7 +70,7 @@ export default function virtualHostsMiddleware() {
6970
logger.debug(
7071
"not authenticated -- denying vhost='%s', path='%s'",
7172
vhost,
72-
path
73+
path,
7374
);
7475
res.status(403).end();
7576
return;

0 commit comments

Comments
 (0)