Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/hub/servers/app/blob-upload.ts
Views: 687
/*1Support user uploading a blob directly from their browser to the CoCalc database,2mainly for markdown documents. This is meant to be very similar to how GitHub3allows for attaching files to github issue comments.4*/56// See also src/packages/project/upload.ts78import { Router } from "express";9import { callback2 } from "@cocalc/util/async-utils";10import { database } from "../database";11const { save_blob } = require("@cocalc/hub/blobs");12import { getLogger } from "@cocalc/hub/logger";13import {14MAX_BLOB_SIZE,15//MAX_BLOB_SIZE_PER_PROJECT_PER_DAY,16} from "@cocalc/util/db-schema/blobs";17import getAccount from "@cocalc/server/auth/get-account";18import isCollaborator from "@cocalc/server/projects/is-collaborator";19import formidable from "formidable";20import { readFile, unlink } from "fs/promises";21import { uuidsha1 } from "@cocalc/backend/misc_node";2223const logger = getLogger("hub:servers:app:blob-upload");24function dbg(...args): void {25logger.debug("upload ", ...args);26}2728export default function init(router: Router) {29router.post("/blobs", async (req, res) => {30const account_id = await getAccount(req);31if (!account_id) {32res.status(500).send("user must be signed in to upload files");33return;34}35const { project_id, ttl } = req.query;36if (!project_id || typeof project_id != "string") {37res.status(500).send("project_id must be specified");38return;39}40if (!(await isCollaborator({ account_id, project_id }))) {41res.status(500).send("user must be collaborator on project");42return;43}4445dbg({ account_id, project_id });4647// TODO: check for throttling/limits48try {49const form = formidable({50keepExtensions: true,51maxFileSize: MAX_BLOB_SIZE,52hashAlgorithm: "sha1",53});5455dbg("parsing form data...");56// https://github.com/node-formidable/formidable?tab=readme-ov-file#parserequest-callback57const [_, files] = await form.parse(req);58//dbg(`finished parsing form data. ${JSON.stringify({ fields, files })}`);5960/* Just for the sake of understanding this, this is how this looks like in the real world (formidable@3):61> files.file[0]62{63size: 80789,64filepath: '/home/hsy/p/cocalc/src/data/projects/c8787b71-a85f-437b-9d1b-29833c3a199e/asdf/asdf/8e3e4367333e45275a8d1aa03.png',65newFilename: '8e3e4367333e45275a8d1aa03.png',66mimetype: 'application/octet-stream',67mtime: '2024-04-23T09:25:53.197Z',68originalFilename: 'Screenshot from 2024-04-23 09-20-40.png'69}70*/71let uuid: string | undefined = undefined;72if (files.file?.[0] != null) {73const { filepath, hash } = files.file[0];74try {75dbg("got", files);76if (typeof hash == "string") {77uuid = uuidsha1("", hash);78}79const blob = await readFile(filepath);80await callback2(save_blob, {81uuid,82blob,83ttl,84project_id,85database,86});87} finally {88try {89await unlink(filepath);90} catch (err) {91dbg("WARNING -- failed to delete uploaded file", err);92}93}94}95if (!uuid) {96res.status(500).send("no file got uploaded");97return;98}99res.send({ uuid });100} catch (err) {101dbg("upload failed ", err);102res.status(500).send(`upload failed -- ${err}`);103}104});105}106107108