@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.
- package/.env.development +9 -0
- package/.prettierignore +4 -0
- package/.prettierrc +8 -0
- package/.vscode/launch.json +20 -0
- package/assets/classroomDesignDoc.js +24 -0
- package/assets/courseValidateDocUpdate.js +56 -0
- package/assets/get-tagsDesignDoc.json +9 -0
- package/babel.config.js +6 -0
- package/dist/app.d.ts +6 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +194 -0
- package/dist/app.js.map +1 -0
- package/dist/attachment-preprocessing/index.d.ts +11 -0
- package/dist/attachment-preprocessing/index.d.ts.map +1 -0
- package/dist/attachment-preprocessing/index.js +146 -0
- package/dist/attachment-preprocessing/index.js.map +1 -0
- package/dist/attachment-preprocessing/normalize.d.ts +7 -0
- package/dist/attachment-preprocessing/normalize.d.ts.map +1 -0
- package/dist/attachment-preprocessing/normalize.js +90 -0
- package/dist/attachment-preprocessing/normalize.js.map +1 -0
- package/dist/client-requests/classroom-requests.d.ts +26 -0
- package/dist/client-requests/classroom-requests.d.ts.map +1 -0
- package/dist/client-requests/classroom-requests.js +171 -0
- package/dist/client-requests/classroom-requests.js.map +1 -0
- package/dist/client-requests/course-requests.d.ts +10 -0
- package/dist/client-requests/course-requests.d.ts.map +1 -0
- package/dist/client-requests/course-requests.js +135 -0
- package/dist/client-requests/course-requests.js.map +1 -0
- package/dist/client.d.ts +31 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +70 -0
- package/dist/client.js.map +1 -0
- package/dist/couchdb/authentication.d.ts +4 -0
- package/dist/couchdb/authentication.d.ts.map +1 -0
- package/dist/couchdb/authentication.js +64 -0
- package/dist/couchdb/authentication.js.map +1 -0
- package/dist/couchdb/index.d.ts +18 -0
- package/dist/couchdb/index.d.ts.map +1 -0
- package/dist/couchdb/index.js +52 -0
- package/dist/couchdb/index.js.map +1 -0
- package/dist/design-docs.d.ts +63 -0
- package/dist/design-docs.d.ts.map +1 -0
- package/dist/design-docs.js +90 -0
- package/dist/design-docs.js.map +1 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +62 -0
- package/dist/logger.js.map +1 -0
- package/dist/routes/logs.d.ts +3 -0
- package/dist/routes/logs.d.ts.map +1 -0
- package/dist/routes/logs.js +274 -0
- package/dist/routes/logs.js.map +1 -0
- package/dist/utils/env.d.ts +10 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +38 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/processQueue.d.ts +39 -0
- package/dist/utils/processQueue.d.ts.map +1 -0
- package/dist/utils/processQueue.js +175 -0
- package/dist/utils/processQueue.js.map +1 -0
- package/eslint.config.js +19 -0
- package/jest.config.ts +24 -0
- package/package.json +74 -0
- package/src/app.ts +246 -0
- package/src/attachment-preprocessing/index.ts +204 -0
- package/src/attachment-preprocessing/normalize.ts +123 -0
- package/src/client-requests/classroom-requests.ts +234 -0
- package/src/client-requests/course-requests.ts +188 -0
- package/src/client.ts +97 -0
- package/src/couchdb/authentication.ts +85 -0
- package/src/couchdb/index.ts +76 -0
- package/src/design-docs.ts +107 -0
- package/src/logger.ts +75 -0
- package/src/routes/logs.ts +289 -0
- package/src/utils/env.ts +51 -0
- package/src/utils/processQueue.ts +218 -0
- package/test/client.test.ts +144 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import logger from '../logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* This queue executes async prcesses sequentially, waiting
|
|
4
|
+
* for each to complete before launching the next.
|
|
5
|
+
*/
|
|
6
|
+
export default class AsyncProcessQueue {
|
|
7
|
+
processRequest;
|
|
8
|
+
queue = [];
|
|
9
|
+
errors = [];
|
|
10
|
+
completed = [];
|
|
11
|
+
processing = false;
|
|
12
|
+
nextID = 0;
|
|
13
|
+
/**
|
|
14
|
+
* Returns 'complete' if the job is complete, 'error' if the
|
|
15
|
+
* job failed, and the job's position in queue if not yet
|
|
16
|
+
* completed.
|
|
17
|
+
*
|
|
18
|
+
* @param jobID The jobID returned by addRequest
|
|
19
|
+
*/
|
|
20
|
+
jobStatus(jobID) {
|
|
21
|
+
let ret = -1; // Default to -1 if job not found
|
|
22
|
+
this.queue.forEach((req) => {
|
|
23
|
+
if (req.id === jobID) {
|
|
24
|
+
ret = this.queue.indexOf(req);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
this.completed.forEach((req) => {
|
|
28
|
+
if (req.id === jobID) {
|
|
29
|
+
ret = 'complete';
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
this.errors.forEach((req) => {
|
|
33
|
+
if (req.id === jobID) {
|
|
34
|
+
ret = 'error';
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return ret;
|
|
38
|
+
}
|
|
39
|
+
async recurseGetResult(jobID, depth) {
|
|
40
|
+
// polling intervals in milliseconds
|
|
41
|
+
logger.info(`Checking job status of job ${jobID}...`);
|
|
42
|
+
const intervals = [100, 200, 400, 800, 1000, 2000, 3000, 5000];
|
|
43
|
+
depth = Math.min(depth, intervals.length - 1);
|
|
44
|
+
let status;
|
|
45
|
+
const p = new Promise((resolve, reject) => {
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
status = this.jobStatus(jobID);
|
|
48
|
+
if (status === 'complete' || status === 'error') {
|
|
49
|
+
resolve(null);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
reject();
|
|
53
|
+
}
|
|
54
|
+
}, intervals[depth]);
|
|
55
|
+
});
|
|
56
|
+
return p
|
|
57
|
+
.then(() => {
|
|
58
|
+
return this.getResult(jobID);
|
|
59
|
+
})
|
|
60
|
+
.catch(() => {
|
|
61
|
+
return this.recurseGetResult(jobID, depth + 1);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async getResult(jobID, _depth = 0) {
|
|
65
|
+
const status = this.jobStatus(jobID);
|
|
66
|
+
if (status === 'complete') {
|
|
67
|
+
const res = this.completed.find((val) => {
|
|
68
|
+
return val.id === jobID;
|
|
69
|
+
});
|
|
70
|
+
if (res) {
|
|
71
|
+
return res.result;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
return {
|
|
75
|
+
error: 'No result found',
|
|
76
|
+
ok: false,
|
|
77
|
+
status: 'error',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (status === 'error') {
|
|
82
|
+
const res = this.errors.find((val) => {
|
|
83
|
+
return val.id === jobID;
|
|
84
|
+
});
|
|
85
|
+
if (!res) {
|
|
86
|
+
return {
|
|
87
|
+
error: 'Job failed - no error log found',
|
|
88
|
+
ok: false,
|
|
89
|
+
status: 'error',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (res.result) {
|
|
93
|
+
return res.result;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// result is null b/c of an uncaugth exception
|
|
97
|
+
return {
|
|
98
|
+
error: res.error,
|
|
99
|
+
ok: false,
|
|
100
|
+
status: 'error',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
return this.recurseGetResult(jobID, 0);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
addRequest(req) {
|
|
109
|
+
const id = this.nextID++;
|
|
110
|
+
this.queue.push({
|
|
111
|
+
id: id,
|
|
112
|
+
request: req,
|
|
113
|
+
});
|
|
114
|
+
if (!this.processing) {
|
|
115
|
+
void this.process();
|
|
116
|
+
}
|
|
117
|
+
return id;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
*
|
|
121
|
+
*/
|
|
122
|
+
constructor(processingFcn) {
|
|
123
|
+
this.processRequest = processingFcn;
|
|
124
|
+
}
|
|
125
|
+
async process() {
|
|
126
|
+
this.processing = true;
|
|
127
|
+
while (this.queue.length > 0) {
|
|
128
|
+
const req = this.queue[0];
|
|
129
|
+
logger.info(`Processing ${req.id}`);
|
|
130
|
+
try {
|
|
131
|
+
const result = await this.processRequest(req.request);
|
|
132
|
+
if (result.ok) {
|
|
133
|
+
this.completed.push({
|
|
134
|
+
id: req.id,
|
|
135
|
+
request: req.request,
|
|
136
|
+
result: result,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
if (result) {
|
|
141
|
+
this.errors.push({
|
|
142
|
+
id: req.id,
|
|
143
|
+
request: req.request,
|
|
144
|
+
result: result,
|
|
145
|
+
error: result.error,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
this.errors.push({
|
|
150
|
+
id: req.id,
|
|
151
|
+
request: req.request,
|
|
152
|
+
error: 'error',
|
|
153
|
+
result: null,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
this.errors.push({
|
|
160
|
+
id: req.id,
|
|
161
|
+
request: req.request,
|
|
162
|
+
result: null,
|
|
163
|
+
error: e,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
// remove the completed (or errored)
|
|
168
|
+
// request from the queue
|
|
169
|
+
this.queue.shift();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
this.processing = false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=processQueue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processQueue.js","sourceRoot":"","sources":["../../src/utils/processQueue.ts"],"names":[],"mappings":"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"}
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import backendConfig from '../../eslint.config.backend.mjs';
|
|
2
|
+
|
|
3
|
+
export default [
|
|
4
|
+
...backendConfig,
|
|
5
|
+
{
|
|
6
|
+
ignores: ['node_modules/**', 'dist/**', 'coverage/**', 'eslint.config.js', 'assets/**', 'babel.config.js', 'jest.config.ts', 'test/**'],
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
languageOptions: {
|
|
10
|
+
parserOptions: {
|
|
11
|
+
project: './tsconfig.json',
|
|
12
|
+
tsconfigRootDir: import.meta.dirname,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
rules: {
|
|
16
|
+
// Express-specific rules (backend config already includes most needed rules)
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
];
|
package/jest.config.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { JestConfigWithTsJest } from 'ts-jest';
|
|
2
|
+
|
|
3
|
+
const config: JestConfigWithTsJest = {
|
|
4
|
+
preset: 'ts-jest',
|
|
5
|
+
testEnvironment: 'node',
|
|
6
|
+
extensionsToTreatAsEsm: ['.ts'],
|
|
7
|
+
moduleNameMapper: {
|
|
8
|
+
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
9
|
+
},
|
|
10
|
+
transform: {
|
|
11
|
+
'^.+\\.tsx?$': [
|
|
12
|
+
'ts-jest',
|
|
13
|
+
{
|
|
14
|
+
useESM: true,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
|
19
|
+
testMatch: ['**/?(*.)+(spec|test).[tj]s?(x)'],
|
|
20
|
+
|
|
21
|
+
rootDir: './',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default config;
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vue-skuilder/express",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "0.1.1",
|
|
7
|
+
"description": "an API",
|
|
8
|
+
"main": "dist/app.js",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"types": "dist/app.d.ts",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"clean": "rimraf dist",
|
|
13
|
+
"dev": "tsx watch src/app.ts",
|
|
14
|
+
"build": "yarn clean && tsc",
|
|
15
|
+
"start": "node dist/app.js",
|
|
16
|
+
"test": "jest --config jest.config.ts",
|
|
17
|
+
"lint": "npx eslint .",
|
|
18
|
+
"lint:fix": "npx eslint . --fix",
|
|
19
|
+
"lint:check": "npx eslint . --max-warnings 0",
|
|
20
|
+
"type-check": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"packageManager": "yarn@4.6.0",
|
|
23
|
+
"author": "Colin Kennedy",
|
|
24
|
+
"license": "GPL-3.0-or-later",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@vue-skuilder/common": "workspace:^",
|
|
27
|
+
"@vue-skuilder/db": "workspace:^",
|
|
28
|
+
"axios": "^1.7.4",
|
|
29
|
+
"cookie-parser": "^1.4.7",
|
|
30
|
+
"cors": "^2.8.5",
|
|
31
|
+
"dotenv": "^16.4.7",
|
|
32
|
+
"express": "^4.21.2",
|
|
33
|
+
"express-rate-limit": "^7.5.0",
|
|
34
|
+
"ffmpeg-static": "^5.2.0",
|
|
35
|
+
"hashids": "^2.3.0",
|
|
36
|
+
"morgan": "^1.10.0",
|
|
37
|
+
"nano": "^ 9.0.5",
|
|
38
|
+
"pouchdb": "9.0.0",
|
|
39
|
+
"winston": "^3.17.0",
|
|
40
|
+
"winston-daily-rotate-file": "^5.0.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@babel/core": "^7.26.9",
|
|
44
|
+
"@babel/preset-env": "^7.26.9",
|
|
45
|
+
"@babel/preset-typescript": "^7.26.0",
|
|
46
|
+
"@tsconfig/node18": "^18.2.4",
|
|
47
|
+
"@types/cookie-parser": "^1.4.8",
|
|
48
|
+
"@types/cors": "^2.8.17",
|
|
49
|
+
"@types/dotenv": "^8.2.3",
|
|
50
|
+
"@types/express": "4.17.21",
|
|
51
|
+
"@types/express-rate-limit": "^6.0.2",
|
|
52
|
+
"@types/ffmpeg-static": "^2.0.0",
|
|
53
|
+
"@types/hashids": "^1.0.30",
|
|
54
|
+
"@types/jest": "^29.5.14",
|
|
55
|
+
"@types/morgan": "^1.9.9",
|
|
56
|
+
"@types/node": "18",
|
|
57
|
+
"@types/pouchdb": "^6.4.2",
|
|
58
|
+
"@typescript-eslint/eslint-plugin": "^8.25.0",
|
|
59
|
+
"@typescript-eslint/parser": "^8.25.0",
|
|
60
|
+
"babel-jest": "^29.6.1",
|
|
61
|
+
"eslint": "^9.21.0",
|
|
62
|
+
"eslint-config-prettier": "^10.0.2",
|
|
63
|
+
"eslint-plugin-prettier": "^5.2.3",
|
|
64
|
+
"jest": "^29.6.1",
|
|
65
|
+
"jest-environment-node": "^29.7.0",
|
|
66
|
+
"prettier": "^3.0.0",
|
|
67
|
+
"rimraf": "*",
|
|
68
|
+
"ts-jest": "^29.2.5",
|
|
69
|
+
"ts-node": "^10.9.2",
|
|
70
|
+
"tsx": "^4.19.2",
|
|
71
|
+
"typescript": "^5.7.2",
|
|
72
|
+
"typescript-eslint": "^8.25.0"
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/app.ts
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ServerRequestType as RequestEnum,
|
|
3
|
+
ServerRequest,
|
|
4
|
+
prepareNote55,
|
|
5
|
+
} from '@vue-skuilder/common';
|
|
6
|
+
import { CourseLookup } from '@vue-skuilder/db';
|
|
7
|
+
import cookieParser from 'cookie-parser';
|
|
8
|
+
import cors from 'cors';
|
|
9
|
+
import type { Request, Response } from 'express';
|
|
10
|
+
import express from 'express';
|
|
11
|
+
import morgan from 'morgan';
|
|
12
|
+
import Nano from 'nano';
|
|
13
|
+
import PostProcess from './attachment-preprocessing/index.js';
|
|
14
|
+
import {
|
|
15
|
+
ClassroomCreationQueue,
|
|
16
|
+
ClassroomJoinQueue,
|
|
17
|
+
ClassroomLeaveQueue,
|
|
18
|
+
} from './client-requests/classroom-requests.js';
|
|
19
|
+
import {
|
|
20
|
+
CourseCreationQueue,
|
|
21
|
+
initCourseDBDesignDocInsert,
|
|
22
|
+
} from './client-requests/course-requests.js';
|
|
23
|
+
import { requestIsAuthenticated } from './couchdb/authentication.js';
|
|
24
|
+
import CouchDB, {
|
|
25
|
+
useOrCreateCourseDB,
|
|
26
|
+
useOrCreateDB,
|
|
27
|
+
} from './couchdb/index.js';
|
|
28
|
+
import logger from './logger.js';
|
|
29
|
+
import logsRouter from './routes/logs.js';
|
|
30
|
+
import ENV from './utils/env.js';
|
|
31
|
+
|
|
32
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
33
|
+
logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
logger.info(`Express app running version: ${ENV.VERSION}`);
|
|
37
|
+
|
|
38
|
+
const port = 3000;
|
|
39
|
+
import { classroomDbDesignDoc } from './design-docs.js';
|
|
40
|
+
const app = express();
|
|
41
|
+
|
|
42
|
+
app.use(cookieParser());
|
|
43
|
+
app.use(express.json());
|
|
44
|
+
app.use(
|
|
45
|
+
cors({
|
|
46
|
+
credentials: true,
|
|
47
|
+
origin: true,
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
app.use(
|
|
51
|
+
morgan('combined', {
|
|
52
|
+
stream: { write: (message: string) => logger.info(message.trim()) },
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
app.use('/logs', logsRouter);
|
|
56
|
+
|
|
57
|
+
export interface VueClientRequest extends express.Request {
|
|
58
|
+
body: ServerRequest;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
app.get('/courses', (_req: Request, res: Response) => {
|
|
62
|
+
void (async () => {
|
|
63
|
+
try {
|
|
64
|
+
const courses = await CourseLookup.allCourses();
|
|
65
|
+
res.send(courses.map((c) => `${c._id} - ${c.name}`));
|
|
66
|
+
} catch (error) {
|
|
67
|
+
logger.error('Error fetching courses:', error);
|
|
68
|
+
res.status(500).send('Failed to fetch courses');
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
app.get('/course/:courseID/config', (req: Request, res: Response) => {
|
|
74
|
+
void (async () => {
|
|
75
|
+
try {
|
|
76
|
+
const courseDB = await useOrCreateCourseDB(req.params.courseID);
|
|
77
|
+
const cfg = await courseDB.get('CourseConfig'); // [ ] pull courseConfig docName into global const
|
|
78
|
+
|
|
79
|
+
res.json(cfg);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
logger.error('Error fetching course config:', error);
|
|
82
|
+
res.status(500).send('Failed to fetch course config');
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
app.delete('/course/:courseID', (req: Request, res: Response) => {
|
|
88
|
+
void (async () => {
|
|
89
|
+
try {
|
|
90
|
+
logger.info(`Delete request made on course ${req.params.courseID}...`);
|
|
91
|
+
const auth = await requestIsAuthenticated(req);
|
|
92
|
+
if (auth) {
|
|
93
|
+
logger.info(`\tAuthenticated delete request made...`);
|
|
94
|
+
const dbResp = await CouchDB.db.destroy(
|
|
95
|
+
`coursedb-${req.params.courseID}`
|
|
96
|
+
);
|
|
97
|
+
if (!dbResp.ok) {
|
|
98
|
+
res.json({ success: false, error: dbResp });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const delResp = await CourseLookup.delete(req.params.courseID);
|
|
102
|
+
|
|
103
|
+
if (delResp.ok) {
|
|
104
|
+
res.json({ success: true });
|
|
105
|
+
} else {
|
|
106
|
+
res.json({ success: false, error: delResp });
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
res.json({ success: false, error: 'Not authenticated' });
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
logger.error('Error deleting course:', error);
|
|
113
|
+
res.status(500).json({ success: false, error: 'Failed to delete course' });
|
|
114
|
+
}
|
|
115
|
+
})();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
async function postHandler(
|
|
119
|
+
req: VueClientRequest,
|
|
120
|
+
res: express.Response
|
|
121
|
+
): Promise<void> {
|
|
122
|
+
const auth = await requestIsAuthenticated(req);
|
|
123
|
+
if (auth) {
|
|
124
|
+
const body = req.body;
|
|
125
|
+
logger.info(
|
|
126
|
+
`Authorized ${
|
|
127
|
+
body.type ? body.type : '[unspecified request type]'
|
|
128
|
+
} request made...`
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (body.type === RequestEnum.CREATE_CLASSROOM) {
|
|
132
|
+
const id: number = ClassroomCreationQueue.addRequest(body.data);
|
|
133
|
+
body.response = await ClassroomCreationQueue.getResult(id);
|
|
134
|
+
res.json(body.response);
|
|
135
|
+
} else if (body.type === RequestEnum.DELETE_CLASSROOM) {
|
|
136
|
+
// [ ] add delete classroom request
|
|
137
|
+
} else if (body.type === RequestEnum.JOIN_CLASSROOM) {
|
|
138
|
+
const id: number = ClassroomJoinQueue.addRequest(body.data);
|
|
139
|
+
body.response = await ClassroomJoinQueue.getResult(id);
|
|
140
|
+
res.json(body.response);
|
|
141
|
+
} else if (body.type === RequestEnum.LEAVE_CLASSROOM) {
|
|
142
|
+
const id: number = ClassroomLeaveQueue.addRequest({
|
|
143
|
+
username: req.body.user,
|
|
144
|
+
...body.data,
|
|
145
|
+
});
|
|
146
|
+
body.response = await ClassroomLeaveQueue.getResult(id);
|
|
147
|
+
res.json(body.response);
|
|
148
|
+
} else if (body.type === RequestEnum.CREATE_COURSE) {
|
|
149
|
+
const id: number = CourseCreationQueue.addRequest(body.data);
|
|
150
|
+
body.response = await CourseCreationQueue.getResult(id);
|
|
151
|
+
res.json(body.response);
|
|
152
|
+
} else if (body.type === RequestEnum.ADD_COURSE_DATA) {
|
|
153
|
+
const payload = prepareNote55(
|
|
154
|
+
body.data.courseID,
|
|
155
|
+
body.data.codeCourse,
|
|
156
|
+
body.data.shape,
|
|
157
|
+
body.data.data,
|
|
158
|
+
body.data.author,
|
|
159
|
+
body.data.tags,
|
|
160
|
+
body.data.uploads
|
|
161
|
+
);
|
|
162
|
+
CouchDB.use(`coursedb-${body.data.courseID}`)
|
|
163
|
+
.insert(payload as Nano.MaybeDocument)
|
|
164
|
+
.then((r) => {
|
|
165
|
+
logger.info(`\t\t\tCouchDB insert result: ${JSON.stringify(r)}`);
|
|
166
|
+
res.json(r);
|
|
167
|
+
})
|
|
168
|
+
.catch((e) => {
|
|
169
|
+
logger.info(`\t\t\tCouchDB insert error: ${JSON.stringify(e)}`);
|
|
170
|
+
res.json(e);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
logger.info(`\tREQUEST UNAUTHORIZED!`);
|
|
175
|
+
res.status(401);
|
|
176
|
+
res.statusMessage = 'Unauthorized';
|
|
177
|
+
res.send();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
app.post('/', (req: Request, res: Response) => {
|
|
182
|
+
void postHandler(req, res);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
app.get('/version', (_req: Request, res: Response) => {
|
|
186
|
+
res.send(ENV.VERSION);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
app.get('/', (_req: Request, res: Response) => {
|
|
190
|
+
let status = `Express service is running.\nVersion: ${ENV.VERSION}\n`;
|
|
191
|
+
|
|
192
|
+
CouchDB.session()
|
|
193
|
+
.then((s) => {
|
|
194
|
+
if (s.ok) {
|
|
195
|
+
status += 'Couchdb is running.\n';
|
|
196
|
+
} else {
|
|
197
|
+
status += 'Couchdb session is NOT ok.\n';
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
.catch((e) => {
|
|
201
|
+
status += `Problems in the couch session! ${JSON.stringify(e)}`;
|
|
202
|
+
})
|
|
203
|
+
.finally(() => {
|
|
204
|
+
res.send(status);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
let listening = false;
|
|
208
|
+
|
|
209
|
+
app.listen(port, () => {
|
|
210
|
+
listening = true;
|
|
211
|
+
logger.info(`Express app listening on port ${port}!`);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
init().catch((e) => {
|
|
215
|
+
logger.error(`Error initializing app: ${JSON.stringify(e)}`);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
async function init(): Promise<void> {
|
|
219
|
+
while (!listening) {
|
|
220
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
// start the change-listener that does post-processing on user
|
|
225
|
+
// media uploads
|
|
226
|
+
void PostProcess();
|
|
227
|
+
|
|
228
|
+
void initCourseDBDesignDocInsert();
|
|
229
|
+
|
|
230
|
+
void useOrCreateDB('classdb-lookup');
|
|
231
|
+
try {
|
|
232
|
+
await (
|
|
233
|
+
await useOrCreateDB('coursedb')
|
|
234
|
+
).insert(
|
|
235
|
+
{
|
|
236
|
+
validate_doc_update: classroomDbDesignDoc,
|
|
237
|
+
} as Nano.MaybeDocument,
|
|
238
|
+
'_design/_auth'
|
|
239
|
+
);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
logger.info(`Error: ${e}`);
|
|
242
|
+
}
|
|
243
|
+
} catch (e) {
|
|
244
|
+
logger.info(`Error: ${JSON.stringify(e)}`);
|
|
245
|
+
}
|
|
246
|
+
}
|