CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/next/pages/api/v2/latex.ts
Views: 687
1
/*
2
Turn LaTeX .tex file contents into a pdf. This run in a CoCalc
3
project with a configurable timeout and command, so can involve
4
arbitrarily sophisticated processing.
5
6
Then the path .tex file is created, if content is specified. Next the command is run which should hopefully produce a pdf file.
7
Finally, the pdf file is read into our database (as a blob).
8
*/
9
10
import getAccountId from "lib/account/get-account";
11
import getOneProject from "@cocalc/server/projects/get-one";
12
import { getProject } from "@cocalc/server/projects/control";
13
import callProject from "@cocalc/server/projects/call";
14
import getParams from "lib/api/get-params";
15
import { path_split } from "@cocalc/util/misc";
16
import getCustomize from "@cocalc/database/settings/customize";
17
import isCollaborator from "@cocalc/server/projects/is-collaborator";
18
import { DEFAULT_LATEX_COMMAND } from "lib/api/latex";
19
20
import { apiRoute, apiRouteOperation } from "lib/api";
21
import {
22
LatexInputSchema,
23
LatexOutputSchema,
24
} from "lib/api/schema/latex";
25
26
27
async function handle(req, res) {
28
const account_id = await getAccountId(req);
29
const params = getParams(req);
30
try {
31
if (!account_id) {
32
throw Error("must be authenticated");
33
}
34
if (!params.path || !params.path.endsWith(".tex")) {
35
throw Error("path must be specified and end in .tex");
36
}
37
const { head: dir, tail: filename } = path_split(params.path);
38
if (params.only_read_pdf) {
39
if (params.project_id == null) {
40
throw Error("if only_read_pdf is set then project_id must also be set");
41
}
42
if (params.path.startsWith("/tmp")) {
43
throw Error(
44
"if only_read_pdf is set then path must not start with /tmp (otherwise the pdf would be removed)",
45
);
46
}
47
}
48
49
let project_id;
50
if (params.project_id != null) {
51
project_id = params.project_id;
52
if (!(await isCollaborator({ project_id, account_id }))) {
53
throw Error("must be signed in as a collaborator on the project");
54
}
55
} else {
56
// don't need to check collaborator in this case:
57
project_id = (await getOneProject(account_id)).project_id;
58
}
59
60
let result: any = undefined;
61
let compile: any = undefined;
62
let pdf: any = undefined;
63
let url: string | undefined = undefined;
64
try {
65
// ensure the project is running.
66
const project = getProject(project_id);
67
await project.start();
68
69
if (!params.only_read_pdf) {
70
if (params.content != null) {
71
// write content to the project as the file path
72
await callProject({
73
account_id,
74
project_id,
75
mesg: {
76
event: "write_text_file_to_project",
77
path: params.path,
78
content: params.content,
79
},
80
});
81
}
82
compile = await callProject({
83
account_id,
84
project_id,
85
mesg: {
86
event: "project_exec",
87
timeout: params.timeout ?? 30,
88
path: dir,
89
command: params.command ?? `${DEFAULT_LATEX_COMMAND} ${filename}`,
90
},
91
});
92
}
93
// TODO: should we check for errors in compile before trying to read pdf?
94
const ttlSeconds = params.ttl ?? 3600;
95
try {
96
pdf = await callProject({
97
account_id,
98
project_id,
99
mesg: {
100
event: "read_file_from_project",
101
path: pdfFile(params.path),
102
ttlSeconds,
103
},
104
});
105
const { siteURL } = await getCustomize();
106
if (pdf != null) {
107
url = pdf.data_uuid
108
? siteURL + `/blobs/${pdfFile(params.path)}?uuid=${pdf.data_uuid}`
109
: undefined;
110
}
111
result = { compile, url, pdf };
112
} catch (err) {
113
result = { compile, error: err.message };
114
}
115
} finally {
116
if (params.path.startsWith("/tmp")) {
117
await callProject({
118
account_id,
119
project_id,
120
mesg: {
121
event: "project_exec",
122
path: "/tmp",
123
bash: true,
124
command: `rm ${rmGlob(params.path)}`,
125
},
126
});
127
}
128
}
129
res.json(result);
130
} catch (err) {
131
res.json({ error: err.message });
132
}
133
}
134
135
function pdfFile(path: string): string {
136
return path.slice(0, path.length - 4) + ".pdf";
137
}
138
139
function rmGlob(path: string): string {
140
return path.slice(0, path.length - 4) + ".*";
141
}
142
143
export default apiRoute({
144
latex: apiRouteOperation({
145
method: "POST",
146
openApiOperation: {
147
tags: ["Utils"]
148
},
149
})
150
.input({
151
contentType: "application/json",
152
body: LatexInputSchema,
153
})
154
.outputs([
155
{
156
status: 200,
157
contentType: "application/json",
158
body: LatexOutputSchema,
159
},
160
])
161
.handler(handle),
162
});
163
164