Skip to content

Commit 6b29c18

Browse files
committed
Jupyter support
1 parent 0a569c8 commit 6b29c18

File tree

3 files changed

+255
-12
lines changed

3 files changed

+255
-12
lines changed

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
],
4242
"dependencies": {
4343
"@fails-components/config": "^1.3.0",
44-
"@fails-components/security": "^1.4.0",
44+
"@fails-components/security": "^1.4.1",
4545
"cors": "^2.8.5",
4646
"express": "^4.21.2",
4747
"moment": "^2.29.4",

src/apphandler.js

Lines changed: 247 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -251,13 +251,115 @@ export class AppHandler {
251251
}
252252
)
253253
}
254+
if (data.ipynbs) {
255+
if (
256+
!data.ipynbs.id ||
257+
typeof data.ipynbs.id !== 'string' ||
258+
data.ipynbs.id.length > 20
259+
)
260+
return res.status(400).send('malformed request')
261+
262+
const pendingupdates = []
263+
const changes = {
264+
$currentDate: { lastaccess: true },
265+
$set: {}
266+
}
267+
let changed = false
268+
if (typeof data.ipynbs.presentDownload !== 'undefined') {
269+
changes.$set['ipynbs.$[notebook].presentDownload'] =
270+
data.ipynbs.presentDownload
271+
changed = true
272+
}
273+
if (typeof data.ipynbs.name !== 'undefined') {
274+
changes.$set['ipynbs.$[notebook].name'] = data.ipynbs.name
275+
changed = true
276+
}
277+
if ('note' in data.ipynbs) {
278+
changes.$set['ipynbs.$[notebook].note'] = data.ipynbs.note
279+
changed = true
280+
}
281+
if (changed) {
282+
pendingupdates.push(
283+
lecturescol.updateOne({ uuid: lectureuuid }, changes, {
284+
arrayFilters: [{ 'notebook.id': data.ipynbs.id }]
285+
})
286+
)
287+
}
288+
289+
if (data.ipynbs.applets) {
290+
data.ipynbs.applets.forEach((applet) => {
291+
if (
292+
typeof applet.presentToStudents !== 'undefined' &&
293+
applet.id
294+
) {
295+
pendingupdates.push(
296+
lecturescol.updateOne(
297+
{ uuid: lectureuuid },
298+
{
299+
$set: {
300+
'ipynbs.$[notebook].applets.$[applet].presentToStudents':
301+
applet.presentToStudents
302+
},
303+
$currentDate: { lastaccess: true }
304+
},
305+
{
306+
arrayFilters: [
307+
{ 'notebook.id': data.ipynbs.id },
308+
{ 'applet.appid': applet.id }
309+
]
310+
}
311+
)
312+
)
313+
}
314+
})
315+
}
316+
await Promise.all(pendingupdates)
317+
/* {
318+
name: el.name,
319+
id: el.id,
320+
mimetype: el.mimetype,
321+
filename: el.filename,
322+
date: el.data,
323+
presentDownload: el.presentDownload,
324+
applets: el.applets?.map?.((applet) => ({
325+
appid: applet.appid,
326+
appname: applet.appname,
327+
presentToStudents: applet.presentToStudents
328+
}))
329+
} */
330+
}
331+
if (data.removeipynb) {
332+
if (data.ipynbs) return res.status(400).send('malformed request') // please not simultaneously
333+
if (
334+
!data.removeipynb.id ||
335+
typeof data.removeipynb.id !== 'string' ||
336+
data.removeipynb.id.length > 20
337+
)
338+
return res.status(400).send('malformed request')
339+
await lecturescol.updateOne(
340+
{ uuid: lectureuuid },
341+
{
342+
$currentDate: { lastaccess: true },
343+
$pull: { ipynbs: { id: data.removeipynb.id } }
344+
}
345+
)
346+
const removed = details?.ipynbs?.find(
347+
(el) => el.id === data.removeipynb.id
348+
)
349+
if (removed?.sha) {
350+
const ipynb = [removed.sha.toString('hex')]
351+
await this.redis.sAdd('checkdel:ipynb', ipynb)
352+
}
353+
}
254354
if (data.polls) {
255355
// console.log(data.polls)
256356
if (
257357
!data.polls.id ||
258358
typeof data.polls.id !== 'string' ||
259-
data.polls.id > 20 ||
260-
(!data.polls.name && !('multi' in data.polls))
359+
data.polls.id.length > 20 ||
360+
(!data.polls.name &&
361+
!('multi' in data.polls) &&
362+
!('note' in data.polls))
261363
)
262364
return res.status(400).send('malformed request')
263365

@@ -297,6 +399,8 @@ export class AppHandler {
297399
const tochange = { $set: {}, $currentDate: { lastaccess: true } }
298400
if (data.polls.name)
299401
tochange.$set['polls.$[pollchange].name'] = data.polls.name
402+
if ('note' in data.polls)
403+
tochange.$set['polls.$[pollchange].note'] = data.polls.note
300404
if ('multi' in data.polls)
301405
tochange.$set['polls.$[pollchange].multi'] = data.polls.multi
302406
await lecturescol.updateOne({ uuid: lectureuuid }, tochange, {
@@ -309,7 +413,7 @@ export class AppHandler {
309413
if (
310414
!data.removepolls.id ||
311415
typeof data.removepolls.id !== 'string' ||
312-
data.removepolls.id > 20
416+
data.removepolls.id.length > 20
313417
)
314418
return res.status(400).send('malformed request')
315419
const tochange = { $currentDate: { lastaccess: true } }
@@ -364,7 +468,40 @@ export class AppHandler {
364468
if (details.date) {
365469
toret.date = details.date
366470
}
367-
// TODO add additional fields for instructor such as pools, pictures, pdfbackground etc.
471+
if (details.ipynbs) {
472+
// 'application/x-ipynb+json'
473+
const instructor = req.token.role.includes('instructor')
474+
const retipynbs = details.ipynbs
475+
.map((el) => {
476+
return {
477+
name: el.name,
478+
id: el.id,
479+
sha: el.sha,
480+
mimetype: el.mimetype,
481+
filename: el.filename,
482+
date: el.data,
483+
presentDownload: el.presentDownload,
484+
note: el.note,
485+
applets: el.applets
486+
?.map?.((applet) => ({
487+
appid: applet.appid,
488+
appname: applet.appname,
489+
presentToStudents: applet.presentToStudents
490+
}))
491+
?.filter?.((applet) => applet.presentToStudents || instructor)
492+
}
493+
})
494+
.filter(
495+
(el) => instructor || el.presentDownload || el.applets?.length > 0
496+
)
497+
.map((el) => ({
498+
sha: el.sha.buffer.toString('hex'),
499+
...el,
500+
url: el.sha && this.getFileURL(el.sha.buffer, el.mimetype)
501+
}))
502+
toret.ipynbs = retipynbs
503+
}
504+
// add additional fields for instructor such as pools, pictures, pdfbackground etc.
368505
if (req.token.role.includes('instructor')) {
369506
// fields only available for the instructors
370507
if (details.pictures) {
@@ -572,10 +709,14 @@ export class AppHandler {
572709
if (req.body.what === 'polls' || req.body.what === 'all') {
573710
filter = { ...filter, polls: 1 }
574711
}
712+
if (req.body.what === 'ipynbs' || req.body.what === 'all') {
713+
filter = { ...filter, ipynbs: 1 }
714+
}
575715
if (req.body.what === 'lecture' || req.body.what === 'all') {
576716
filter = {
577717
...filter,
578718
usedpictures: 1,
719+
usedipynbs: 1,
579720
backgroundpdfuse: 1,
580721
backgroundpdf: 1,
581722
boards: 1,
@@ -632,6 +773,24 @@ export class AppHandler {
632773
)
633774
}
634775
}
776+
if (req.body.what === 'ipynbs' || req.body.what === 'all') {
777+
if (lecturedoc.ipynbs) {
778+
const toremove = lecturedoc.ipynbs.map((el) => el.sha)
779+
promis.push(
780+
lecturescol.updateOne(
781+
{ uuid: lectureuuid },
782+
{ $pull: { ipynbs: { sha: { $in: toremove } } } }
783+
)
784+
)
785+
// now we removed dublettes, we can insert the current ones
786+
promis.push(
787+
lecturescol.updateOne(
788+
{ uuid: lectureuuid },
789+
{ $push: { ipynbs: { $each: lecturedoc.ipynbs } } }
790+
)
791+
)
792+
}
793+
}
635794
if (req.body.what === 'lecture' || req.body.what === 'all') {
636795
const set = {}
637796
if (lecturedoc.backgroundpdfuse) set.backgroundpdfuse = 1
@@ -640,6 +799,7 @@ export class AppHandler {
640799
if (lecturedoc.boardsavetime)
641800
set.boardsavetime = lecturedoc.boardsavetime
642801
if (lecturedoc.usedpictures) set.usedpictures = lecturedoc.usedpictures
802+
if (lecturedoc.usedipynbs) set.usedipynbs = lecturedoc.ipynbs
643803

644804
const boards = []
645805
if (lecturedoc.boards) {
@@ -788,6 +948,89 @@ export class AppHandler {
788948
return res.status(500).send('error converting ' + error)
789949
}
790950
})
951+
952+
app.post(path + '/lecture/ipynb', async (req, res) => {
953+
if (!req.token.role.includes('instructor'))
954+
return res.status(401).send('unauthorized')
955+
const lectureuuid = req.token.course.lectureuuid
956+
if (!isUUID(lectureuuid)) return res.status(401).send('unauthorized uuid') // supply valid data
957+
958+
// first we have to check, whether some information about the applets is already set
959+
const details = await this.getLectureDetails(lectureuuid)
960+
if (!details) return res.status(404).send('not found')
961+
try {
962+
const body = {}
963+
const [{ sha256, mimeType } = {}] = await this.handleFileUpload(
964+
req,
965+
body,
966+
{ filename: true, id: true, applets: true, name: true },
967+
{},
968+
['file'],
969+
this.maxFileSize,
970+
['application/x-ipynb+json']
971+
)
972+
973+
const applets = JSON.parse(body.applets)
974+
let oldsha
975+
const oldNotebook = details?.ipynbs?.find((el) => el.id === body.id)
976+
if (oldNotebook?.sha) {
977+
oldsha = oldNotebook?.sha
978+
}
979+
applets?.forEach?.((applet) => {
980+
if (typeof applet.presentToStudents !== 'undefined') return
981+
const oldApplet = oldNotebook?.applets?.find?.(
982+
(appl) => appl.appid === applet.appid
983+
)
984+
if (typeof oldApplet?.presentToStudents !== 'undefined')
985+
applet.presentToStudents = oldApplet.presentToStudents
986+
else applet.presentToStudents = false
987+
})
988+
const pynb = {
989+
id: body.id,
990+
name: body.name,
991+
sha: sha256,
992+
mimetype: mimeType,
993+
filename: body.filename,
994+
presentDownload:
995+
oldNotebook?.presentDownload || body.presentDownload || 'no',
996+
note: oldNotebook?.note || '',
997+
applets
998+
}
999+
const lecturescol = this.mongo.collection('lectures')
1000+
// date
1001+
if (!details?.ipynbs) {
1002+
await lecturescol.updateOne(
1003+
{ uuid: lectureuuid },
1004+
{
1005+
$addToSet: {
1006+
ipynbs: { id: pynb.id }
1007+
},
1008+
$currentDate: { lastaccess: true }
1009+
}
1010+
)
1011+
}
1012+
await lecturescol.updateOne(
1013+
{ uuid: lectureuuid },
1014+
{
1015+
$set: {
1016+
'ipynbs.$[elem]': { id: pynb.id, ...pynb }
1017+
},
1018+
$currentDate: { lastaccess: true }
1019+
},
1020+
{
1021+
arrayFilters: [{ 'elem.id': pynb.id }]
1022+
}
1023+
)
1024+
if (oldsha) {
1025+
const ipynb = [oldsha.toString('hex')]
1026+
await this.redis.sAdd('checkdel:ipynb', ipynb)
1027+
}
1028+
return res.status(200).json({}) // no return just success
1029+
} catch (error) {
1030+
console.log('upload ipynb error', error)
1031+
return res.status(500).send('error uploading ipynb ' + error)
1032+
}
1033+
})
7911034
}
7921035

7931036
async getLectureDetails(uuid) {

0 commit comments

Comments
 (0)