UNPKG

smc-hub

Version:

CoCalc: Backend webserver component

133 lines (120 loc) 4.72 kB
/* * This file is part of CoCalc: Copyright © 2020 Sagemath, Inc. * License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details */ /* Information (from the database) about authors of shares, and what shares were authored by a given account. */ import { callback2 } from "smc-util/async-utils"; import { cmp, endswith, is_valid_uuid_string, meta_file } from "smc-util/misc"; import { Author } from "smc-webapp/share/types"; import { Database } from "./types"; export class AuthorInfo { private database: Database; constructor(database: Database) { this.database = database; } public async get_authors( project_id: string, // path can be a single path or an array of paths; // if give a single path, also automatically includes // known aux files (this is just for ipynb). path: string | string[] ): Promise<Author[]> { if (!is_valid_uuid_string(project_id)) { throw Error(`project_id=${project_id} must be a valid uuid string`); } // Determine the paths to check in the database: const account_ids: string[] = []; const known_account_ids: Set<string> = new Set(); let paths: string[]; if (typeof path == "string") { paths = [path]; if (endswith(path, ".ipynb")) { paths.push(meta_file(path, "jupyter2")); paths.push(meta_file(path, "jupyter")); } } else { paths = path; } const collabs = new Set( await callback2(this.database.get_collaborators.bind(this.database), { project_id, }) ); // Get accounts that have edited these paths, if they have edited them using sync. for (const path of paths) { const id: string = this.database.sha1(project_id, path); const result = await callback2(this.database._query, { query: `SELECT users FROM syncstrings WHERE string_id='${id}'`, }); if (result == null || result.rowCount < 1) continue; for (const account_id of result.rows[0].users) { if ( account_id != project_id && !known_account_ids.has(account_id) && collabs.has(account_id) ) { account_ids.push(account_id); known_account_ids.add(account_id); } } } // If no accounts, use the project collaborators as a fallback. if (account_ids.length === 0) { const result = await callback2(this.database._query, { query: `SELECT jsonb_object_keys(users) FROM projects where project_id='${project_id}'`, }); if (result != null && result.rowCount >= 1) { for (const v of result.rows) { account_ids.push(v.jsonb_object_keys); } } } // Get usernames for the accounts const authors: Author[] = []; const names = await callback2(this.database.get_usernames, { account_ids, cache_time_s: 60 * 5, }); for (const account_id in names) { // todo really need to sort by last name const { first_name, last_name } = names[account_id]; const name = `${first_name} ${last_name}`; authors.push({ name, account_id }); } // Sort by last name authors.sort((a, b) => cmp(names[a.account_id].last_name, names[b.account_id].last_name) ); return authors; } public async get_username(account_id: string): Promise<string> { const names = await callback2(this.database.get_usernames, { account_ids: [account_id], cache_time_s: 60 * 5, }); const { first_name, last_name } = names[account_id]; return `${first_name} ${last_name}`; } public async get_shares(account_id: string): Promise<string[]> { // Returns the id's of all public paths for which account_id // is a collaborator on the project that has actively used the project. // It would be more useful // to additionally filter using the syncstrings table for documents // that account_id actually edited, but that's a lot harder. // We sort from most recently saved back. if (!is_valid_uuid_string(account_id)) { throw Error(`account_id=${account_id} must be a valid uuid string`); } const query = `SELECT public_paths.id FROM public_paths, projects WHERE public_paths.project_id = projects.project_id AND projects.last_active ? '${account_id}' AND projects.users ? '${account_id}' AND (public_paths.unlisted is null OR public_paths.unlisted = false) AND (public_paths.disabled is null OR public_paths.disabled = false) ORDER BY public_paths.last_edited DESC`; const result = await callback2(this.database._query, { query }); const ids: string[] = []; if (result == null) return []; for (const x of result.rows) { ids.push(x.id); } return ids; } }