Skip to content

Commit df21cf7

Browse files
committed
refactor: use app-factory isntance to execute main
1 parent 1a866eb commit df21cf7

File tree

3 files changed

+24
-248
lines changed

3 files changed

+24
-248
lines changed

express-api-todo.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,18 @@
2828
- VueClientRequest interface moved to factory for shared usage
2929

3030
### 1.2b Refactor Existing App to Use Factory
31-
- [ ] Refactor `src/app.ts` to use `createExpressApp()` from app-factory
32-
- [ ] Replace duplicated middleware/routes with factory function call
33-
- [ ] Use `initializeServices()` instead of inline init() function
34-
- [ ] Ensure existing platform behavior unchanged
35-
- [ ] Test that `yarn dev` still works correctly
31+
- [x] Refactor `src/app.ts` to use `createExpressApp()` from app-factory
32+
- [x] Replace duplicated middleware/routes with factory function call
33+
- [x] Use `initializeServices()` instead of inline init() function
34+
- [x] Ensure existing platform behavior unchanged
35+
- [x] Test that `yarn dev` still works correctly
36+
37+
**Summary**: Completely refactored standalone app.ts to eliminate duplication:
38+
- Reduced from ~264 lines to ~32 lines (87% reduction)
39+
- Uses `createExpressApp(ENV)` with environment config
40+
- Uses `initializeServices()` for background initialization
41+
- Preserves original port (3000), logging, and error handling behavior
42+
- Both modes now guaranteed to use identical Express configuration and routes
3643

3744
**Note**: This eliminates code duplication and ensures both standalone and programmatic modes use identical logic.
3845

packages/express/src/app.ts

Lines changed: 11 additions & 242 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,5 @@
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';
292
import logger from './logger.js';
30-
import logsRouter from './routes/logs.js';
313
import ENV from './utils/env.js';
324

335
process.on('unhandledRejection', (reason, promise) => {
@@ -37,227 +9,24 @@ process.on('unhandledRejection', (reason, promise) => {
379
logger.info(`Express app running version: ${ENV.VERSION}`);
3810

3911
const 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-
});
22412
let listening = false;
22513

14+
// Use the factory with environment config
15+
const app = createExpressApp(ENV);
16+
22617
app.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+
});

packages/express/src/couchdb/authentication.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Nano from 'nano';
22
import { COUCH_URL_WITH_PROTOCOL } from './index.js';
3-
import { VueClientRequest } from '../app.js';
3+
import { VueClientRequest } from '../app-factory.js';
44
import logger from '../logger.js';
55

66
interface CouchSession {

0 commit comments

Comments
 (0)