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 { packCourse } from './client-requests/pack-requests.js' ;
24- import { requestIsAuthenticated } from './couchdb/authentication.js' ;
25- import CouchDB , {
26- useOrCreateCourseDB ,
27- useOrCreateDB ,
28- } from './couchdb/index.js' ;
1+ import { createExpressApp , initializeServices } from './app-factory.js' ;
292import logger from './logger.js' ;
30- import logsRouter from './routes/logs.js' ;
313import ENV from './utils/env.js' ;
324
335process . on ( 'unhandledRejection' , ( reason , promise ) => {
@@ -37,227 +9,24 @@ process.on('unhandledRejection', (reason, promise) => {
379logger . info ( `Express app running version: ${ ENV . VERSION } ` ) ;
3810
3911const port = 3000 ;
40- import { classroomDbDesignDoc } from './design-docs.js' ;
41- const app = express ( ) ;
42-
43- app . use ( cookieParser ( ) ) ;
44- app . use ( express . json ( ) ) ;
45- app . use (
46- cors ( {
47- credentials : true ,
48- origin : true ,
49- } )
50- ) ;
51- app . use (
52- morgan ( 'combined' , {
53- stream : { write : ( message : string ) => logger . info ( message . trim ( ) ) } ,
54- } )
55- ) ;
56- app . use ( '/logs' , logsRouter ) ;
57-
58- export interface VueClientRequest extends express . Request {
59- body : ServerRequest ;
60- }
61-
62- app . get ( '/courses' , ( _req : Request , res : Response ) => {
63- void ( async ( ) => {
64- try {
65- const courses = await CourseLookup . allCourseWare ( ) ;
66- res . send ( courses . map ( ( c ) => `${ c . _id } - ${ c . name } ` ) ) ;
67- } catch ( error ) {
68- logger . error ( 'Error fetching courses:' , error ) ;
69- res . status ( 500 ) . send ( 'Failed to fetch courses' ) ;
70- }
71- } ) ( ) ;
72- } ) ;
73-
74- app . get ( '/course/:courseID/config' , ( req : Request , res : Response ) => {
75- void ( async ( ) => {
76- try {
77- const courseDB = await useOrCreateCourseDB ( req . params . courseID ) ;
78- const cfg = await courseDB . get ( 'CourseConfig' ) ; // [ ] pull courseConfig docName into global const
79-
80- res . json ( cfg ) ;
81- } catch ( error ) {
82- logger . error ( 'Error fetching course config:' , error ) ;
83- res . status ( 500 ) . send ( 'Failed to fetch course config' ) ;
84- }
85- } ) ( ) ;
86- } ) ;
87-
88- app . delete ( '/course/:courseID' , ( req : Request , res : Response ) => {
89- void ( async ( ) => {
90- try {
91- logger . info ( `Delete request made on course ${ req . params . courseID } ...` ) ;
92- const auth = await requestIsAuthenticated ( req ) ;
93- if ( auth ) {
94- logger . info ( `\tAuthenticated delete request made...` ) ;
95- const dbResp = await CouchDB . db . destroy (
96- `coursedb-${ req . params . courseID } `
97- ) ;
98- if ( ! dbResp . ok ) {
99- res . json ( { success : false , error : dbResp } ) ;
100- return ;
101- }
102- const delResp = await CourseLookup . delete ( req . params . courseID ) ;
103-
104- if ( delResp . ok ) {
105- res . json ( { success : true } ) ;
106- } else {
107- res . json ( { success : false , error : delResp } ) ;
108- }
109- } else {
110- res . json ( { success : false , error : 'Not authenticated' } ) ;
111- }
112- } catch ( error ) {
113- logger . error ( 'Error deleting course:' , error ) ;
114- res . status ( 500 ) . json ( { success : false , error : 'Failed to delete course' } ) ;
115- }
116- } ) ( ) ;
117- } ) ;
118-
119- async function postHandler (
120- req : VueClientRequest ,
121- res : express . Response
122- ) : Promise < void > {
123- const auth = await requestIsAuthenticated ( req ) ;
124- if ( auth ) {
125- const body = req . body ;
126- logger . info (
127- `Authorized ${
128- body . type ? body . type : '[unspecified request type]'
129- } request made...`
130- ) ;
131-
132- if ( body . type === RequestEnum . CREATE_CLASSROOM ) {
133- const id : number = ClassroomCreationQueue . addRequest ( body . data ) ;
134- body . response = await ClassroomCreationQueue . getResult ( id ) ;
135- res . json ( body . response ) ;
136- } else if ( body . type === RequestEnum . DELETE_CLASSROOM ) {
137- // [ ] add delete classroom request
138- } else if ( body . type === RequestEnum . JOIN_CLASSROOM ) {
139- const id : number = ClassroomJoinQueue . addRequest ( body . data ) ;
140- body . response = await ClassroomJoinQueue . getResult ( id ) ;
141- res . json ( body . response ) ;
142- } else if ( body . type === RequestEnum . LEAVE_CLASSROOM ) {
143- const id : number = ClassroomLeaveQueue . addRequest ( {
144- username : req . body . user ,
145- ...body . data ,
146- } ) ;
147- body . response = await ClassroomLeaveQueue . getResult ( id ) ;
148- res . json ( body . response ) ;
149- } else if ( body . type === RequestEnum . CREATE_COURSE ) {
150- const id : number = CourseCreationQueue . addRequest ( body . data ) ;
151- body . response = await CourseCreationQueue . getResult ( id ) ;
152- res . json ( body . response ) ;
153- } else if ( body . type === RequestEnum . ADD_COURSE_DATA ) {
154- const payload = prepareNote55 (
155- body . data . courseID ,
156- body . data . codeCourse ,
157- body . data . shape ,
158- body . data . data ,
159- body . data . author ,
160- body . data . tags ,
161- body . data . uploads
162- ) ;
163- CouchDB . use ( `coursedb-${ body . data . courseID } ` )
164- . insert ( payload as Nano . MaybeDocument )
165- . then ( ( r ) => {
166- logger . info ( `\t\t\tCouchDB insert result: ${ JSON . stringify ( r ) } ` ) ;
167- res . json ( r ) ;
168- } )
169- . catch ( ( e ) => {
170- logger . info ( `\t\t\tCouchDB insert error: ${ JSON . stringify ( e ) } ` ) ;
171- res . json ( e ) ;
172- } ) ;
173- } else if ( body . type === RequestEnum . PACK_COURSE ) {
174- if ( process . env . NODE_ENV !== 'studio' ) {
175- logger . info (
176- `\tPACK_COURSE request received in production mode, but this is not supported!`
177- ) ;
178- res . status ( 400 ) ;
179- res . statusMessage = 'Packing courses is not supported in production mode.' ;
180- res . send ( ) ;
181- return ;
182- }
183-
184- body . response = await packCourse ( {
185- courseId : body . courseId ,
186- outputPath : body . outputPath
187- } ) ;
188- res . json ( body . response ) ;
189- }
190- } else {
191- logger . info ( `\tREQUEST UNAUTHORIZED!` ) ;
192- res . status ( 401 ) ;
193- res . statusMessage = 'Unauthorized' ;
194- res . send ( ) ;
195- }
196- }
197-
198- app . post ( '/' , ( req : Request , res : Response ) => {
199- void postHandler ( req , res ) ;
200- } ) ;
201-
202- app . get ( '/version' , ( _req : Request , res : Response ) => {
203- res . send ( ENV . VERSION ) ;
204- } ) ;
205-
206- app . get ( '/' , ( _req : Request , res : Response ) => {
207- let status = `Express service is running.\nVersion: ${ ENV . VERSION } \n` ;
208-
209- CouchDB . session ( )
210- . then ( ( s ) => {
211- if ( s . ok ) {
212- status += 'Couchdb is running.\n' ;
213- } else {
214- status += 'Couchdb session is NOT ok.\n' ;
215- }
216- } )
217- . catch ( ( e ) => {
218- status += `Problems in the couch session! ${ JSON . stringify ( e ) } ` ;
219- } )
220- . finally ( ( ) => {
221- res . send ( status ) ;
222- } ) ;
223- } ) ;
22412let listening = false ;
22513
14+ // Use the factory with environment config
15+ const app = createExpressApp ( ENV ) ;
16+
22617app . listen ( port , ( ) => {
22718 listening = true ;
22819 logger . info ( `Express app listening on port ${ port } !` ) ;
22920} ) ;
23021
231- init ( ) . catch ( ( e ) => {
232- logger . error ( `Error initializing app: ${ JSON . stringify ( e ) } ` ) ;
233- } ) ;
234-
235- async function init ( ) : Promise < void > {
22+ // Initialize services after startup
23+ const init = async ( ) : Promise < void > => {
23624 while ( ! listening ) {
23725 await new Promise ( ( resolve ) => setTimeout ( resolve , 100 ) ) ;
23826 }
27+ await initializeServices ( ) ;
28+ } ;
23929
240- try {
241- // start the change-listener that does post-processing on user
242- // media uploads
243- void PostProcess ( ) ;
244-
245- void initCourseDBDesignDocInsert ( ) ;
246-
247- void useOrCreateDB ( 'classdb-lookup' ) ;
248- try {
249- await (
250- await useOrCreateDB ( 'coursedb' )
251- ) . insert (
252- {
253- validate_doc_update : classroomDbDesignDoc ,
254- } as Nano . MaybeDocument ,
255- '_design/_auth'
256- ) ;
257- } catch ( e ) {
258- logger . info ( `Error: ${ e } ` ) ;
259- }
260- } catch ( e ) {
261- logger . info ( `Error: ${ JSON . stringify ( e ) } ` ) ;
262- }
263- }
30+ init ( ) . catch ( ( e ) => {
31+ logger . error ( `Error initializing app: ${ JSON . stringify ( e ) } ` ) ;
32+ } ) ;
0 commit comments