@vue-skuilder/express 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.env.development +9 -0
  2. package/.prettierignore +4 -0
  3. package/.prettierrc +8 -0
  4. package/.vscode/launch.json +20 -0
  5. package/assets/classroomDesignDoc.js +24 -0
  6. package/assets/courseValidateDocUpdate.js +56 -0
  7. package/assets/get-tagsDesignDoc.json +9 -0
  8. package/babel.config.js +6 -0
  9. package/dist/app.d.ts +6 -0
  10. package/dist/app.d.ts.map +1 -0
  11. package/dist/app.js +194 -0
  12. package/dist/app.js.map +1 -0
  13. package/dist/attachment-preprocessing/index.d.ts +11 -0
  14. package/dist/attachment-preprocessing/index.d.ts.map +1 -0
  15. package/dist/attachment-preprocessing/index.js +146 -0
  16. package/dist/attachment-preprocessing/index.js.map +1 -0
  17. package/dist/attachment-preprocessing/normalize.d.ts +7 -0
  18. package/dist/attachment-preprocessing/normalize.d.ts.map +1 -0
  19. package/dist/attachment-preprocessing/normalize.js +90 -0
  20. package/dist/attachment-preprocessing/normalize.js.map +1 -0
  21. package/dist/client-requests/classroom-requests.d.ts +26 -0
  22. package/dist/client-requests/classroom-requests.d.ts.map +1 -0
  23. package/dist/client-requests/classroom-requests.js +171 -0
  24. package/dist/client-requests/classroom-requests.js.map +1 -0
  25. package/dist/client-requests/course-requests.d.ts +10 -0
  26. package/dist/client-requests/course-requests.d.ts.map +1 -0
  27. package/dist/client-requests/course-requests.js +135 -0
  28. package/dist/client-requests/course-requests.js.map +1 -0
  29. package/dist/client.d.ts +31 -0
  30. package/dist/client.d.ts.map +1 -0
  31. package/dist/client.js +70 -0
  32. package/dist/client.js.map +1 -0
  33. package/dist/couchdb/authentication.d.ts +4 -0
  34. package/dist/couchdb/authentication.d.ts.map +1 -0
  35. package/dist/couchdb/authentication.js +64 -0
  36. package/dist/couchdb/authentication.js.map +1 -0
  37. package/dist/couchdb/index.d.ts +18 -0
  38. package/dist/couchdb/index.d.ts.map +1 -0
  39. package/dist/couchdb/index.js +52 -0
  40. package/dist/couchdb/index.js.map +1 -0
  41. package/dist/design-docs.d.ts +63 -0
  42. package/dist/design-docs.d.ts.map +1 -0
  43. package/dist/design-docs.js +90 -0
  44. package/dist/design-docs.js.map +1 -0
  45. package/dist/logger.d.ts +3 -0
  46. package/dist/logger.d.ts.map +1 -0
  47. package/dist/logger.js +62 -0
  48. package/dist/logger.js.map +1 -0
  49. package/dist/routes/logs.d.ts +3 -0
  50. package/dist/routes/logs.d.ts.map +1 -0
  51. package/dist/routes/logs.js +274 -0
  52. package/dist/routes/logs.js.map +1 -0
  53. package/dist/utils/env.d.ts +10 -0
  54. package/dist/utils/env.d.ts.map +1 -0
  55. package/dist/utils/env.js +38 -0
  56. package/dist/utils/env.js.map +1 -0
  57. package/dist/utils/processQueue.d.ts +39 -0
  58. package/dist/utils/processQueue.d.ts.map +1 -0
  59. package/dist/utils/processQueue.js +175 -0
  60. package/dist/utils/processQueue.js.map +1 -0
  61. package/eslint.config.js +19 -0
  62. package/jest.config.ts +24 -0
  63. package/package.json +74 -0
  64. package/src/app.ts +246 -0
  65. package/src/attachment-preprocessing/index.ts +204 -0
  66. package/src/attachment-preprocessing/normalize.ts +123 -0
  67. package/src/client-requests/classroom-requests.ts +234 -0
  68. package/src/client-requests/course-requests.ts +188 -0
  69. package/src/client.ts +97 -0
  70. package/src/couchdb/authentication.ts +85 -0
  71. package/src/couchdb/index.ts +76 -0
  72. package/src/design-docs.ts +107 -0
  73. package/src/logger.ts +75 -0
  74. package/src/routes/logs.ts +289 -0
  75. package/src/utils/env.ts +51 -0
  76. package/src/utils/processQueue.ts +218 -0
  77. package/test/client.test.ts +144 -0
  78. package/tsconfig.json +27 -0
@@ -0,0 +1,9 @@
1
+ # This config aligns with the `dev` script at the project root,
2
+ # and assumes that the CouchDB backend is available
3
+
4
+ COUCHDB_SERVER=localhost:5984
5
+ COUCHDB_PROTOCOL=http
6
+ COUCHDB_ADMIN=admin
7
+ COUCHDB_PASSWORD=password
8
+
9
+ VERSION=localdev
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ dist/
3
+ coverage/
4
+ assets/*.js
package/.prettierrc ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "semi": true,
3
+ "trailingComma": "es5",
4
+ "singleQuote": true,
5
+ "printWidth": 80,
6
+ "tabWidth": 2,
7
+ "bracketSpacing": true
8
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "type": "node",
9
+ "request": "launch",
10
+ "name": "Launch Program",
11
+ "skipFiles": [
12
+ "<node_internals>/**"
13
+ ],
14
+ "program": "${workspaceFolder}/src/app.ts",
15
+ "outFiles": [
16
+ "${workspaceFolder}/**/*.js"
17
+ ]
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,24 @@
1
+ function (newDoc, oldDoc, userCtx, secObj) {
2
+ // from https://github.com/iriscouch/manage_couchdb/blob/master/js/validate_doc_update.js
3
+
4
+ var ddoc = this;
5
+
6
+ secObj.admins = secObj.admins || {};
7
+ secObj.admins.names = secObj.admins.names || [];
8
+ secObj.admins.roles = secObj.admins.roles || [];
9
+
10
+ var IS_DB_ADMIN = false;
11
+ if (~userCtx.roles.indexOf('_admin'))
12
+ IS_DB_ADMIN = true;
13
+ if (~secObj.admins.names.indexOf(userCtx.name))
14
+ IS_DB_ADMIN = true;
15
+ for (var i = 0; i < userCtx.roles; i++)
16
+ if (~secObj.admins.roles.indexOf(userCtx.roles[i]))
17
+ IS_DB_ADMIN = true;
18
+
19
+ if (ddoc.access && ddoc.access.read_only)
20
+ if (IS_DB_ADMIN)
21
+ log('Admin change on read-only db: ' + newDoc._id);
22
+ else
23
+ throw { 'forbidden': 'This database is read-only' };
24
+ }
@@ -0,0 +1,56 @@
1
+ function(newDoc, oldDoc, userCtx, secObj) {
2
+ // Skip validation for deletions
3
+ if (newDoc._deleted) return;
4
+
5
+ // Always allow admins to do anything
6
+ if (userCtx.roles.indexOf('_admin') !== -1) return;
7
+
8
+ // For CourseConfig document - we need special handling
9
+ if (newDoc._id === 'CourseConfig') {
10
+ // Allow the creator or admins listed in the document to modify it
11
+ if (oldDoc && oldDoc.creator === userCtx.name) return;
12
+ if (oldDoc && oldDoc.admins && Array.isArray(oldDoc.admins) && oldDoc.admins.indexOf(userCtx.name) !== -1) return;
13
+
14
+ // For updates, if user is not creator or admin, deny
15
+ if (oldDoc) {
16
+ throw({forbidden: "Only course creator or admins can modify course configuration"});
17
+ }
18
+
19
+ // For new course config, allow (initial creation is secured at API level)
20
+ return;
21
+ }
22
+
23
+ // For all other documents
24
+ var isAdmin = false;
25
+ var isModerator = false;
26
+
27
+ // Course admins and moderators can edit anything
28
+ // (Since we can't check CourseConfig directly, we rely on document author for regular docs)
29
+
30
+ // Document has author field that matches current user - allow
31
+ if (oldDoc && oldDoc.author === userCtx.name) return;
32
+
33
+ // Allow document creation by any authenticated user
34
+ if (!oldDoc) {
35
+ if (!userCtx.name) {
36
+ throw({forbidden: "You must be logged in to create documents"});
37
+ }
38
+
39
+ // Ensure new documents have an author field that matches the current user
40
+ if (!newDoc.author || newDoc.author !== userCtx.name) {
41
+ throw({forbidden: "Document author must match your username"});
42
+ }
43
+
44
+ return;
45
+ }
46
+
47
+ // For updates to existing documents, deny if not the original author
48
+ if (oldDoc && oldDoc.author && oldDoc.author !== userCtx.name) {
49
+ throw({forbidden: "You can only modify your own documents"});
50
+ }
51
+
52
+ // Special case for design documents - only admins can modify (handled above)
53
+ if (newDoc._id.startsWith('_design/')) {
54
+ throw({forbidden: "Only admins can modify design documents"});
55
+ }
56
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "_id": "_design/getTags",
3
+ "views": {
4
+ "get-tags": {
5
+ "map": "function (doc) {\n if (doc.docType && doc.docType === \"TAG\"\n) {\n for (var cardIndex in doc.taggedCards){\n emit(doc.taggedCards[cardIndex], {\n docType: doc.docType,\n name: doc.name,\n snippit: doc.snippit,\n wiki: '',\n taggedCards: []\n });\n }\n }\n}"
6
+ }
7
+ },
8
+ "language": "javascript"
9
+ }
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ presets: [
3
+ ['@babel/preset-env', {targets: {node: 'current'}}],
4
+ '@babel/preset-typescript',
5
+ ],
6
+ };
package/dist/app.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { ServerRequest } from '@vue-skuilder/common';
2
+ import express from 'express';
3
+ export interface VueClientRequest extends express.Request {
4
+ body: ServerRequest;
5
+ }
6
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EAEd,MAAM,sBAAsB,CAAC;AAK9B,OAAO,OAAO,MAAM,SAAS,CAAC;AA+C9B,MAAM,WAAW,gBAAiB,SAAQ,OAAO,CAAC,OAAO;IACvD,IAAI,EAAE,aAAa,CAAC;CACrB"}
package/dist/app.js ADDED
@@ -0,0 +1,194 @@
1
+ import { ServerRequestType as RequestEnum, prepareNote55, } from '@vue-skuilder/common';
2
+ import { CourseLookup } from '@vue-skuilder/db';
3
+ import cookieParser from 'cookie-parser';
4
+ import cors from 'cors';
5
+ import express from 'express';
6
+ import morgan from 'morgan';
7
+ import PostProcess from './attachment-preprocessing/index.js';
8
+ import { ClassroomCreationQueue, ClassroomJoinQueue, ClassroomLeaveQueue, } from './client-requests/classroom-requests.js';
9
+ import { CourseCreationQueue, initCourseDBDesignDocInsert, } from './client-requests/course-requests.js';
10
+ import { requestIsAuthenticated } from './couchdb/authentication.js';
11
+ import CouchDB, { useOrCreateCourseDB, useOrCreateDB, } from './couchdb/index.js';
12
+ import logger from './logger.js';
13
+ import logsRouter from './routes/logs.js';
14
+ import ENV from './utils/env.js';
15
+ process.on('unhandledRejection', (reason, promise) => {
16
+ logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
17
+ });
18
+ logger.info(`Express app running version: ${ENV.VERSION}`);
19
+ const port = 3000;
20
+ import { classroomDbDesignDoc } from './design-docs.js';
21
+ const app = express();
22
+ app.use(cookieParser());
23
+ app.use(express.json());
24
+ app.use(cors({
25
+ credentials: true,
26
+ origin: true,
27
+ }));
28
+ app.use(morgan('combined', {
29
+ stream: { write: (message) => logger.info(message.trim()) },
30
+ }));
31
+ app.use('/logs', logsRouter);
32
+ app.get('/courses', (_req, res) => {
33
+ void (async () => {
34
+ try {
35
+ const courses = await CourseLookup.allCourses();
36
+ res.send(courses.map((c) => `${c._id} - ${c.name}`));
37
+ }
38
+ catch (error) {
39
+ logger.error('Error fetching courses:', error);
40
+ res.status(500).send('Failed to fetch courses');
41
+ }
42
+ })();
43
+ });
44
+ app.get('/course/:courseID/config', (req, res) => {
45
+ void (async () => {
46
+ try {
47
+ const courseDB = await useOrCreateCourseDB(req.params.courseID);
48
+ const cfg = await courseDB.get('CourseConfig'); // [ ] pull courseConfig docName into global const
49
+ res.json(cfg);
50
+ }
51
+ catch (error) {
52
+ logger.error('Error fetching course config:', error);
53
+ res.status(500).send('Failed to fetch course config');
54
+ }
55
+ })();
56
+ });
57
+ app.delete('/course/:courseID', (req, res) => {
58
+ void (async () => {
59
+ try {
60
+ logger.info(`Delete request made on course ${req.params.courseID}...`);
61
+ const auth = await requestIsAuthenticated(req);
62
+ if (auth) {
63
+ logger.info(`\tAuthenticated delete request made...`);
64
+ const dbResp = await CouchDB.db.destroy(`coursedb-${req.params.courseID}`);
65
+ if (!dbResp.ok) {
66
+ res.json({ success: false, error: dbResp });
67
+ return;
68
+ }
69
+ const delResp = await CourseLookup.delete(req.params.courseID);
70
+ if (delResp.ok) {
71
+ res.json({ success: true });
72
+ }
73
+ else {
74
+ res.json({ success: false, error: delResp });
75
+ }
76
+ }
77
+ else {
78
+ res.json({ success: false, error: 'Not authenticated' });
79
+ }
80
+ }
81
+ catch (error) {
82
+ logger.error('Error deleting course:', error);
83
+ res.status(500).json({ success: false, error: 'Failed to delete course' });
84
+ }
85
+ })();
86
+ });
87
+ async function postHandler(req, res) {
88
+ const auth = await requestIsAuthenticated(req);
89
+ if (auth) {
90
+ const body = req.body;
91
+ logger.info(`Authorized ${body.type ? body.type : '[unspecified request type]'} request made...`);
92
+ if (body.type === RequestEnum.CREATE_CLASSROOM) {
93
+ const id = ClassroomCreationQueue.addRequest(body.data);
94
+ body.response = await ClassroomCreationQueue.getResult(id);
95
+ res.json(body.response);
96
+ }
97
+ else if (body.type === RequestEnum.DELETE_CLASSROOM) {
98
+ // [ ] add delete classroom request
99
+ }
100
+ else if (body.type === RequestEnum.JOIN_CLASSROOM) {
101
+ const id = ClassroomJoinQueue.addRequest(body.data);
102
+ body.response = await ClassroomJoinQueue.getResult(id);
103
+ res.json(body.response);
104
+ }
105
+ else if (body.type === RequestEnum.LEAVE_CLASSROOM) {
106
+ const id = ClassroomLeaveQueue.addRequest({
107
+ username: req.body.user,
108
+ ...body.data,
109
+ });
110
+ body.response = await ClassroomLeaveQueue.getResult(id);
111
+ res.json(body.response);
112
+ }
113
+ else if (body.type === RequestEnum.CREATE_COURSE) {
114
+ const id = CourseCreationQueue.addRequest(body.data);
115
+ body.response = await CourseCreationQueue.getResult(id);
116
+ res.json(body.response);
117
+ }
118
+ else if (body.type === RequestEnum.ADD_COURSE_DATA) {
119
+ const payload = prepareNote55(body.data.courseID, body.data.codeCourse, body.data.shape, body.data.data, body.data.author, body.data.tags, body.data.uploads);
120
+ CouchDB.use(`coursedb-${body.data.courseID}`)
121
+ .insert(payload)
122
+ .then((r) => {
123
+ logger.info(`\t\t\tCouchDB insert result: ${JSON.stringify(r)}`);
124
+ res.json(r);
125
+ })
126
+ .catch((e) => {
127
+ logger.info(`\t\t\tCouchDB insert error: ${JSON.stringify(e)}`);
128
+ res.json(e);
129
+ });
130
+ }
131
+ }
132
+ else {
133
+ logger.info(`\tREQUEST UNAUTHORIZED!`);
134
+ res.status(401);
135
+ res.statusMessage = 'Unauthorized';
136
+ res.send();
137
+ }
138
+ }
139
+ app.post('/', (req, res) => {
140
+ void postHandler(req, res);
141
+ });
142
+ app.get('/version', (_req, res) => {
143
+ res.send(ENV.VERSION);
144
+ });
145
+ app.get('/', (_req, res) => {
146
+ let status = `Express service is running.\nVersion: ${ENV.VERSION}\n`;
147
+ CouchDB.session()
148
+ .then((s) => {
149
+ if (s.ok) {
150
+ status += 'Couchdb is running.\n';
151
+ }
152
+ else {
153
+ status += 'Couchdb session is NOT ok.\n';
154
+ }
155
+ })
156
+ .catch((e) => {
157
+ status += `Problems in the couch session! ${JSON.stringify(e)}`;
158
+ })
159
+ .finally(() => {
160
+ res.send(status);
161
+ });
162
+ });
163
+ let listening = false;
164
+ app.listen(port, () => {
165
+ listening = true;
166
+ logger.info(`Express app listening on port ${port}!`);
167
+ });
168
+ init().catch((e) => {
169
+ logger.error(`Error initializing app: ${JSON.stringify(e)}`);
170
+ });
171
+ async function init() {
172
+ while (!listening) {
173
+ await new Promise((resolve) => setTimeout(resolve, 100));
174
+ }
175
+ try {
176
+ // start the change-listener that does post-processing on user
177
+ // media uploads
178
+ void PostProcess();
179
+ void initCourseDBDesignDocInsert();
180
+ void useOrCreateDB('classdb-lookup');
181
+ try {
182
+ await (await useOrCreateDB('coursedb')).insert({
183
+ validate_doc_update: classroomDbDesignDoc,
184
+ }, '_design/_auth');
185
+ }
186
+ catch (e) {
187
+ logger.info(`Error: ${e}`);
188
+ }
189
+ }
190
+ catch (e) {
191
+ logger.info(`Error: ${JSON.stringify(e)}`);
192
+ }
193
+ }
194
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Apply post-processing to a course database. Runs continuously.
3
+ * @param courseID
4
+ */
5
+ export declare function postProcessCourse(courseID: string): void;
6
+ /**
7
+ * Connect to CouchDB, monitor changes to uploaded card data,
8
+ * perform post-processing on uploaded media
9
+ */
10
+ export default function postProcess(): Promise<void>;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/attachment-preprocessing/index.ts"],"names":[],"mappings":"AA0BA;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CA2BxD;AAED;;;GAGG;AACH,wBAA8B,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAiBzD"}
@@ -0,0 +1,146 @@
1
+ import CouchDB from '../couchdb/index.js';
2
+ import { normalize } from './normalize.js';
3
+ import AsyncProcessQueue from '../utils/processQueue.js';
4
+ import logger from '../logger.js';
5
+ import { CourseLookup } from '@vue-skuilder/db';
6
+ // @ts-expect-error [todo]
7
+ const Q = new AsyncProcessQueue(processDocAttachments);
8
+ /**
9
+ * Apply post-processing to a course database. Runs continuously.
10
+ * @param courseID
11
+ */
12
+ export function postProcessCourse(courseID) {
13
+ try {
14
+ logger.info(`Following course ${courseID}`);
15
+ const crsString = `coursedb-${courseID}`;
16
+ // Get database instance
17
+ const db = CouchDB.use(crsString);
18
+ const courseFilter = filterFactory(courseID);
19
+ db.changesReader
20
+ .start({
21
+ // feed: 'continuous',
22
+ includeDocs: false,
23
+ })
24
+ .on('change', (change) => {
25
+ courseFilter(change).catch((e) => {
26
+ logger.error(`Error in CourseFilter for ${courseID}: ${e}`);
27
+ });
28
+ })
29
+ .on('error', (err) => {
30
+ logger.error(`Error in changes feed for ${crsString}: ${err}`);
31
+ });
32
+ }
33
+ catch (e) {
34
+ logger.error(`Error in postProcessCourse: ${e}`);
35
+ }
36
+ }
37
+ /**
38
+ * Connect to CouchDB, monitor changes to uploaded card data,
39
+ * perform post-processing on uploaded media
40
+ */
41
+ export default async function postProcess() {
42
+ try {
43
+ logger.info(`Following all course databases for changes...`);
44
+ const courses = await CourseLookup.allCourses();
45
+ for (const course of courses) {
46
+ try {
47
+ postProcessCourse(course._id);
48
+ }
49
+ catch (e) {
50
+ logger.error(`Error processing course ${course._id}: ${e}`);
51
+ throw e;
52
+ }
53
+ }
54
+ }
55
+ catch (e) {
56
+ logger.error(`Error in postProcess: ${e}`);
57
+ }
58
+ }
59
+ function filterFactory(courseID) {
60
+ const courseDatabase = CouchDB.use(`coursedb-${courseID}`);
61
+ return async function filterChanges(changeItem) {
62
+ try {
63
+ const docNoAttachments = await courseDatabase.get(changeItem.id, {
64
+ attachments: false,
65
+ });
66
+ if (docNoAttachments._attachments &&
67
+ Object.keys(docNoAttachments._attachments).length > 0 &&
68
+ (docNoAttachments['processed'] === undefined ||
69
+ docNoAttachments['processed'] === false)) {
70
+ const doc = await courseDatabase.get(changeItem.id, {
71
+ attachments: true,
72
+ });
73
+ const processingRequest = {
74
+ courseID,
75
+ docID: doc._id,
76
+ fields: [],
77
+ };
78
+ const atts = doc._attachments;
79
+ for (const attachment in atts) {
80
+ const content_type = atts[attachment]['content_type'];
81
+ logger.info(`Course: ${courseID}\n\tAttachment ${attachment} in:\n\t${doc._id}\n should be processed...`);
82
+ if (content_type.includes('audio')) {
83
+ processingRequest.fields.push({
84
+ name: attachment,
85
+ mimetype: content_type,
86
+ });
87
+ }
88
+ }
89
+ Q.addRequest(processingRequest);
90
+ }
91
+ }
92
+ catch (e) {
93
+ logger.error(`Error processing doc ${changeItem.id}: ${e}`);
94
+ }
95
+ };
96
+ }
97
+ async function processDocAttachments(request) {
98
+ if (request.fields.length == 0) {
99
+ logger.info(`No attachments to process for ${request.docID}`);
100
+ return {
101
+ error: 'No attachments to process',
102
+ ok: true,
103
+ status: 'warning',
104
+ };
105
+ }
106
+ const courseDatabase = CouchDB.use(`coursedb-${request.courseID}`);
107
+ const doc = await courseDatabase.get(request.docID, {
108
+ attachments: true,
109
+ att_encoding_info: true,
110
+ });
111
+ for (const field of request.fields) {
112
+ logger.info(`Converting ${field.name}`);
113
+ const attachment = doc._attachments[field.name].data;
114
+ if (field.mimetype.includes('audio')) {
115
+ try {
116
+ const converted = await normalize(attachment);
117
+ field.returnData = converted;
118
+ }
119
+ catch (e) {
120
+ logger.info(`Exception caught: ${e}`);
121
+ throw e;
122
+ }
123
+ }
124
+ }
125
+ logger.info('Conversions finished');
126
+ request.fields.forEach((field) => {
127
+ logger.info(`Replacing doc Data for ${field.name}`);
128
+ if (doc['processed']) {
129
+ doc['processed'].push(field.name);
130
+ }
131
+ else {
132
+ doc['processed'] = [field.name];
133
+ }
134
+ doc._attachments[field.name].data = field.returnData;
135
+ });
136
+ // request was a noop.
137
+ // Mark as processed in order to avoid inifinte loop
138
+ if (request.fields.length === 0) {
139
+ doc['processed'] = true;
140
+ }
141
+ const resp = (await courseDatabase.insert(doc));
142
+ resp.status = 'ok';
143
+ logger.info(`Processing request reinsert result: ${JSON.stringify(resp)}`);
144
+ return resp;
145
+ }
146
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/attachment-preprocessing/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Returns normalized, base-64 encoded mp3
3
+ *
4
+ * @param fileData the base-64 encoded mp3 data from couchdb
5
+ */
6
+ export declare function normalize(fileData: string): Promise<string>;
7
+ //# sourceMappingURL=normalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/attachment-preprocessing/normalize.ts"],"names":[],"mappings":"AAgEA;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAqDjE"}
@@ -0,0 +1,90 @@
1
+ import fs from 'fs';
2
+ import logger from '../logger.js';
3
+ import { promisify } from 'util';
4
+ import { exec as execCallback } from 'child_process';
5
+ const exec = promisify(execCallback);
6
+ import FFMPEGstatic from 'ffmpeg-static';
7
+ if (!FFMPEGstatic) {
8
+ const e = 'FFMPEGstatic executable not found';
9
+ logger.error(e);
10
+ throw new Error(e);
11
+ }
12
+ // string | null here - but we know it's a string from the above check
13
+ const FFMPEG = FFMPEGstatic;
14
+ logger.info(`FFMPEG path: ${FFMPEG}`);
15
+ checkFFMPEGVersion().catch((e) => {
16
+ const msg = 'FFMPEG version check failed';
17
+ logger.error(msg, e);
18
+ throw new Error(msg + e);
19
+ });
20
+ async function checkFFMPEGVersion() {
21
+ try {
22
+ if (!fs.existsSync(FFMPEG)) {
23
+ const e = `FFMPEG executable not found at path: ${FFMPEG}`;
24
+ logger.error(e);
25
+ throw new Error(e);
26
+ }
27
+ const result = await exec(`${FFMPEG} -version`);
28
+ const version = result.stdout.split('\n')[0];
29
+ logger.info(`FFMPEG version: ${version}`);
30
+ // Verify loudnorm filter availability
31
+ const filters = await exec(`${FFMPEG} -filters | grep loudnorm`);
32
+ if (!filters.stdout.includes('loudnorm')) {
33
+ throw new Error('loudnorm filter not available');
34
+ }
35
+ }
36
+ catch (error) {
37
+ logger.error('FFMPEG version check failed:', error);
38
+ throw error;
39
+ }
40
+ }
41
+ /**
42
+ * Returns normalized, base-64 encoded mp3
43
+ *
44
+ * @param fileData the base-64 encoded mp3 data from couchdb
45
+ */
46
+ export async function normalize(fileData) {
47
+ const encoding = 'base64';
48
+ const tmpDir = fs.mkdtempSync(`audioNormalize-${encoding}-`);
49
+ const fileName = tmpDir + '/file.mp3';
50
+ fs.writeFileSync(fileName, fileData, {
51
+ encoding,
52
+ });
53
+ const ext = '.' + fileName.split('.')[1];
54
+ const PADDED = tmpDir + '/padded' + ext;
55
+ const PADDED_NORMALIZED = tmpDir + '/paddedNormalized' + ext;
56
+ const NORMALIZED = tmpDir + '/normalized' + ext;
57
+ try {
58
+ // elongate
59
+ await exec(FFMPEG + ` -i ${fileName} -af "adelay=10000|10000" ${PADDED}`);
60
+ const info = await exec(FFMPEG +
61
+ ` -i ${PADDED} -af loudnorm=I=-16:TP=-1.5:LRA=11:print_format=json -f null -`);
62
+ const data = JSON.parse(info.stderr.substring(info.stderr.indexOf('{')));
63
+ // normalize the elongated file
64
+ await exec(FFMPEG +
65
+ ` -i ${PADDED} -af ` +
66
+ `loudnorm=I=-16:TP=-1.5:LRA=11:measured_I=${data.input_i}:` +
67
+ `measured_LRA=${data.input_lra}:measured_TP=${data.input_tp}:` +
68
+ `measured_thresh=${data.input_thresh}:offset=${data.target_offset}:linear=true:` +
69
+ `print_format=summary -ar 48k ${PADDED_NORMALIZED}`);
70
+ // cut off the elongated part
71
+ await exec(FFMPEG +
72
+ ` -i ${PADDED_NORMALIZED} -ss 00:00:10.000 -acodec copy ${NORMALIZED}`);
73
+ const ret = fs.readFileSync(NORMALIZED, {
74
+ encoding,
75
+ });
76
+ return ret;
77
+ }
78
+ catch (e) {
79
+ logger.error(e);
80
+ throw e;
81
+ }
82
+ finally {
83
+ const files = fs.readdirSync(tmpDir);
84
+ files.forEach((file) => {
85
+ fs.unlinkSync(tmpDir + '/' + file);
86
+ });
87
+ fs.rmdirSync(tmpDir);
88
+ }
89
+ }
90
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/attachment-preprocessing/normalize.ts"],"names":[],"mappings":"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"}