linguabot commited on
Commit
e080d3e
·
verified ·
1 Parent(s): 2b03dcf

Upload folder using huggingface_hub

Browse files
models/RefinityAnnotation.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const mongoose = require('mongoose');
2
+
3
+ const RefinityAnnotationSchema = new mongoose.Schema({
4
+ versionId: { type: mongoose.Schema.Types.ObjectId, ref: 'RefinityVersion', required: true },
5
+ start: { type: Number, required: true },
6
+ end: { type: Number, required: true },
7
+ category: { type: String, required: true },
8
+ comment: { type: String },
9
+ }, { timestamps: true });
10
+
11
+ module.exports = mongoose.model('RefinityAnnotation', RefinityAnnotationSchema);
12
+
13
+
package-lock.json CHANGED
@@ -19,6 +19,7 @@
19
  "express-rate-limit": "^7.1.5",
20
  "googleapis": "^118.0.0",
21
  "jsonwebtoken": "^9.0.2",
 
22
  "mammoth": "^1.6.0",
23
  "mongoose": "^8.0.3",
24
  "multer": "^1.4.5-lts.1",
 
19
  "express-rate-limit": "^7.1.5",
20
  "googleapis": "^118.0.0",
21
  "jsonwebtoken": "^9.0.2",
22
+ "jszip": "^3.10.1",
23
  "mammoth": "^1.6.0",
24
  "mongoose": "^8.0.3",
25
  "multer": "^1.4.5-lts.1",
routes/auth.js CHANGED
@@ -164,9 +164,9 @@ router.get('/admin/users', authenticateToken, async (req, res) => {
164
  // Fallback to predefined if DB empty
165
  if (!users || users.length === 0) {
166
  users = Object.values(PREDEFINED_USERS).map(u => ({
167
- ...u,
168
- online: !!(ONLINE_USERS[u.email] && (now - ONLINE_USERS[u.email] < ONLINE_WINDOW_MS))
169
- }));
170
  }
171
  res.json({ success: true, users });
172
  } catch (error) {
@@ -216,9 +216,9 @@ router.put('/admin/users/:email', authenticateToken, async (req, res) => {
216
  // Sync legacy map as best-effort
217
  if (!PREDEFINED_USERS[email]) PREDEFINED_USERS[email] = { name: name || '', email, role: role || 'student', ...(displayName ? { displayName } : { displayName: name }) };
218
  else {
219
- if (typeof name === 'string' && name.trim()) PREDEFINED_USERS[email].name = name.trim();
220
- if (typeof role === 'string' && role.trim()) PREDEFINED_USERS[email].role = role.trim();
221
- if (typeof displayName === 'string') PREDEFINED_USERS[email].displayName = displayName;
222
  }
223
  res.json({ success: true, user: updated || PREDEFINED_USERS[email] });
224
  } catch (error) {
 
164
  // Fallback to predefined if DB empty
165
  if (!users || users.length === 0) {
166
  users = Object.values(PREDEFINED_USERS).map(u => ({
167
+ ...u,
168
+ online: !!(ONLINE_USERS[u.email] && (now - ONLINE_USERS[u.email] < ONLINE_WINDOW_MS))
169
+ }));
170
  }
171
  res.json({ success: true, users });
172
  } catch (error) {
 
216
  // Sync legacy map as best-effort
217
  if (!PREDEFINED_USERS[email]) PREDEFINED_USERS[email] = { name: name || '', email, role: role || 'student', ...(displayName ? { displayName } : { displayName: name }) };
218
  else {
219
+ if (typeof name === 'string' && name.trim()) PREDEFINED_USERS[email].name = name.trim();
220
+ if (typeof role === 'string' && role.trim()) PREDEFINED_USERS[email].role = role.trim();
221
+ if (typeof displayName === 'string') PREDEFINED_USERS[email].displayName = displayName;
222
  }
223
  res.json({ success: true, user: updated || PREDEFINED_USERS[email] });
224
  } catch (error) {
routes/docs.js CHANGED
@@ -98,7 +98,7 @@ router.post('/create', authenticateToken, async (req, res) => {
98
  if (err && err.code === 11000) {
99
  const fallbackId = `${baseDocId}-${wn}-${gn}-${Date.now()}`;
100
  const savedManual = await GroupDoc.create({ ...payload, docId: fallbackId });
101
- return res.status(201).json({ success: true, doc: savedManual });
102
  }
103
  throw err;
104
  }
 
98
  if (err && err.code === 11000) {
99
  const fallbackId = `${baseDocId}-${wn}-${gn}-${Date.now()}`;
100
  const savedManual = await GroupDoc.create({ ...payload, docId: fallbackId });
101
+ return res.status(201).json({ success: true, doc: savedManual });
102
  }
103
  throw err;
104
  }
routes/refinity.js CHANGED
@@ -9,6 +9,7 @@ const router = express.Router();
9
  const RefinityTask = require('../models/RefinityTask');
10
  const RefinityVersion = require('../models/RefinityVersion');
11
  const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 8 * 1024 * 1024 } });
 
12
 
13
  // Parse .docx or .doc (best effort: .doc handled via mammoth may fail depending on content)
14
  router.post('/parse', upload.single('file'), async (req, res) => {
@@ -173,6 +174,33 @@ router.post('/export-plain', async (req, res) => {
173
  }
174
  });
175
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  // True OOXML revisions (w:ins/w:del) + comments in sidebar
177
  router.post('/track-changes-ooxml', async (req, res) => {
178
  try {
@@ -397,6 +425,58 @@ router.put('/versions/:id', async (req, res) => {
397
  }
398
  });
399
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  function escapeHtml(str='') {
401
  return str
402
  .replace(/&/g, '&amp;')
 
9
  const RefinityTask = require('../models/RefinityTask');
10
  const RefinityVersion = require('../models/RefinityVersion');
11
  const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 8 * 1024 * 1024 } });
12
+ const RefinityAnnotation = require('../models/RefinityAnnotation');
13
 
14
  // Parse .docx or .doc (best effort: .doc handled via mammoth may fail depending on content)
15
  router.post('/parse', upload.single('file'), async (req, res) => {
 
174
  }
175
  });
176
 
177
+ // Export plain .docx with appended annotations list at the end
178
+ router.post('/export-plain-with-annotations', async (req, res) => {
179
+ try {
180
+ const current = String(req.body?.current || '');
181
+ const annotations = Array.isArray(req.body?.annotations) ? req.body.annotations : [];
182
+ const outName = String(req.body?.filename || 'refinity-with-annotations.docx').replace(/[\/]+/g,'_');
183
+ const paraFromLine = (line) => new Paragraph({ children: [ new TextRun({ text: line }) ] });
184
+ const mainParas = String(current).split(/\r?\n/).map(paraFromLine);
185
+ const sections = [
186
+ ...mainParas.length ? mainParas : [ new Paragraph('') ],
187
+ new Paragraph({ children: [ new TextRun({ text: '' }) ] }),
188
+ new Paragraph({ children: [ new TextRun({ text: 'Annotations', bold: true }) ] }),
189
+ ];
190
+ annotations.forEach((a, idx) => {
191
+ const label = `${idx+1}. [${String(a.category||'other')}] ${String(a.comment||'')}`;
192
+ sections.push(new Paragraph({ children: [ new TextRun({ text: label }) ] }));
193
+ });
194
+ const doc = new Document({ sections: [ { properties: {}, children: sections } ] });
195
+ const buffer = await Packer.toBuffer(doc);
196
+ res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
197
+ res.setHeader('Content-Disposition', `attachment; filename="${outName}"`);
198
+ res.send(Buffer.from(buffer));
199
+ } catch (e) {
200
+ res.status(500).json({ error: 'Failed to export document with annotations' });
201
+ }
202
+ });
203
+
204
  // True OOXML revisions (w:ins/w:del) + comments in sidebar
205
  router.post('/track-changes-ooxml', async (req, res) => {
206
  try {
 
425
  }
426
  });
427
 
428
+ // ----- Annotation APIs -----
429
+ router.get('/annotations', async (req, res) => {
430
+ try {
431
+ const versionId = String(req.query?.versionId || '');
432
+ if (!versionId) return res.json([]);
433
+ const rows = await RefinityAnnotation.find({ versionId }).sort({ createdAt: 1 });
434
+ res.json(rows);
435
+ } catch (e) {
436
+ res.status(500).json({ error: 'Failed to load annotations' });
437
+ }
438
+ });
439
+
440
+ router.post('/annotations', async (req, res) => {
441
+ try {
442
+ const { versionId, start, end, category, comment } = req.body || {};
443
+ if (!versionId || start === undefined || end === undefined || !category) {
444
+ return res.status(400).json({ error: 'Missing required fields' });
445
+ }
446
+ const row = await RefinityAnnotation.create({ versionId, start, end, category, comment });
447
+ res.json(row);
448
+ } catch (e) {
449
+ res.status(500).json({ error: 'Failed to create annotation' });
450
+ }
451
+ });
452
+
453
+ router.put('/annotations/:id', async (req, res) => {
454
+ try {
455
+ const { id } = req.params;
456
+ const row = await RefinityAnnotation.findById(id);
457
+ if (!row) return res.status(404).json({ error: 'Annotation not found' });
458
+ const { start, end, category, comment } = req.body || {};
459
+ if (start !== undefined) row.start = Number(start);
460
+ if (end !== undefined) row.end = Number(end);
461
+ if (category !== undefined) row.category = String(category);
462
+ if (comment !== undefined) row.comment = String(comment);
463
+ await row.save();
464
+ res.json(row);
465
+ } catch (e) {
466
+ res.status(500).json({ error: 'Failed to update annotation' });
467
+ }
468
+ });
469
+
470
+ router.delete('/annotations/:id', async (req, res) => {
471
+ try {
472
+ const { id } = req.params;
473
+ await RefinityAnnotation.deleteOne({ _id: id });
474
+ res.json({ ok: true });
475
+ } catch (e) {
476
+ res.status(500).json({ error: 'Failed to delete annotation' });
477
+ }
478
+ });
479
+
480
  function escapeHtml(str='') {
481
  return str
482
  .replace(/&/g, '&amp;')