Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/master/src/packages/hub/servers/app/blob-upload.ts
Views: 923
/*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 (typeof project_id == 'string' && project_id) {37if (!(await isCollaborator({ account_id, project_id }))) {38res.status(500).send("user must be collaborator on project");39return;40}41}4243dbg({ account_id, project_id });4445// TODO: check for throttling/limits46try {47const form = formidable({48keepExtensions: true,49maxFileSize: MAX_BLOB_SIZE,50hashAlgorithm: "sha1",51});5253dbg("parsing form data...");54// https://github.com/node-formidable/formidable?tab=readme-ov-file#parserequest-callback55const [_, files] = await form.parse(req);56//dbg(`finished parsing form data. ${JSON.stringify({ fields, files })}`);5758/* Just for the sake of understanding this, this is how this looks like in the real world (formidable@3):59> files.file[0]60{61size: 80789,62filepath: '/home/hsy/p/cocalc/src/data/projects/c8787b71-a85f-437b-9d1b-29833c3a199e/asdf/asdf/8e3e4367333e45275a8d1aa03.png',63newFilename: '8e3e4367333e45275a8d1aa03.png',64mimetype: 'application/octet-stream',65mtime: '2024-04-23T09:25:53.197Z',66originalFilename: 'Screenshot from 2024-04-23 09-20-40.png'67}68*/69let uuid: string | undefined = undefined;70if (files.file?.[0] != null) {71const { filepath, hash } = files.file[0];72try {73dbg("got", files);74if (typeof hash == "string") {75uuid = uuidsha1("", hash);76}77const blob = await readFile(filepath);78await callback2(save_blob, {79uuid,80blob,81ttl,82project_id,83database,84account_id,85});86} finally {87try {88await unlink(filepath);89} catch (err) {90dbg("WARNING -- failed to delete uploaded file", err);91}92}93}94if (!uuid) {95res.status(500).send("no file got uploaded");96return;97}98res.send({ uuid });99} catch (err) {100dbg("upload failed ", err);101res.status(500).send(`upload failed -- ${err}`);102}103});104}105106107