Skip to content

Commit 094671b

Browse files
committed
feat(history-service),fix(question-service): add all endpoints, models, and routes for history service and fixed test cases and moved env files for question-service
1 parent f2233c5 commit 094671b

23 files changed

+614
-247
lines changed

docker-compose.yml

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,24 @@ services:
115115
dockerfile: Dockerfile
116116
command: ["npm", "test"]
117117
env_file:
118-
- ./question-service/.env
118+
- ./question-service/.env.test
119119
depends_on:
120120
question-db-pg:
121121
condition: service_healthy
122122
networks:
123123
- app-network
124124

125-
125+
# --- UI for Postgres Database ---
126+
adminer:
127+
image: adminer
128+
container_name: adminer-ui
129+
restart: unless-stopped
130+
ports:
131+
- "8087:8080"
132+
networks:
133+
- app-network
134+
depends_on:
135+
- question-db-pg
126136

127137
# Matching service
128138
matching-service:
@@ -164,6 +174,29 @@ services:
164174
networks:
165175
- app-network
166176

177+
# History service
178+
history-service:
179+
build:
180+
context: ./history-service
181+
dockerfile: Dockerfile
182+
ports:
183+
- '8085:8085'
184+
environment:
185+
- PORT=8085
186+
- MONGO_URI=${HISTORY_DB_MONGO_URI}
187+
env_file:
188+
- ./.env
189+
depends_on:
190+
- user-db-mongodb
191+
volumes:
192+
- ./history-service:/app
193+
- /app/node_modules
194+
networks:
195+
- app-network
196+
command: npm run dev
197+
198+
199+
167200
networks:
168201
app-network:
169202
driver: bridge

history-service/.dockerignore

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Dependencies
2+
node_modules
3+
npm-debug.log*
4+
yarn-debug.log*
5+
yarn-error.log*
6+
7+
# Build outputs
8+
dist
9+
build
10+
*.tsbuildinfo
11+
12+
# Environment files
13+
.env
14+
.env.local
15+
.env.development.local
16+
.env.test.local
17+
.env.production.local
18+
19+
# IDE files
20+
.vscode
21+
.idea
22+
*.swp
23+
*.swo
24+
25+
# OS files
26+
.DS_Store
27+
Thumbs.db
28+
29+
# Git
30+
.git
31+
.gitignore
32+
33+
# Docker
34+
Dockerfile
35+
.dockerignore
36+
docker-compose*.yml
37+
38+
# Logs
39+
logs
40+
*.log
41+
42+
# Runtime data
43+
pids
44+
*.pid
45+
*.seed
46+
*.pid.lock
47+
48+
# Coverage directory used by tools like istanbul
49+
coverage
50+
51+
# Dependency directories
52+
jspm_packages/
53+
54+
# Optional npm cache directory
55+
.npm
56+
57+
# Optional eslint cache
58+
.eslintcache

history-service/Dockerfile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Dockerfile for backend services (user-service, question-service, matching-service, collaboration-service)
2+
FROM node:22-alpine
3+
4+
# Set working directory
5+
WORKDIR /app
6+
7+
# Copy package.json and package-lock.json (if available)
8+
COPY package*.json ./
9+
10+
# Install dependencies
11+
RUN npm install
12+
13+
# Copy source code
14+
COPY . .
15+
16+
# Expose the port (will be overridden by docker-compose for each service)
17+
EXPOSE 8085
18+
19+
# Start the application
20+
CMD ["npm", "run", "dev"]

history-service/package.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "history-service",
3+
"version": "1.0.0",
4+
"main": "dist/index.js",
5+
"scripts": {
6+
"start": "node dist/index.js",
7+
"dev": "ts-node-dev src/index.ts",
8+
"build": "tsc"
9+
},
10+
"dependencies": {
11+
"cors": "^2.8.5",
12+
"express": "^4.18.2",
13+
"mongoose": "^8.0.0"
14+
},
15+
"devDependencies": {
16+
"@types/cors": "^2.8.17",
17+
"@types/express": "^4.17.21",
18+
"@types/node": "^20.10.0",
19+
"ts-node-dev": "^2.0.0",
20+
"typescript": "^5.3.2"
21+
}
22+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { Request, Response } from 'express';
2+
import { HistoryService } from '../services/history.service';
3+
4+
export class HistoryController {
5+
private historyService: HistoryService;
6+
7+
constructor() {
8+
this.historyService = new HistoryService();
9+
}
10+
11+
// Endpoint for Matching Service to GET history
12+
public getUserHistory = async (req: Request, res: Response): Promise<Response> => {
13+
try {
14+
const userId = req.params['userId'];
15+
if (!userId) {
16+
return res.status(400).json({ message: 'userId is required in the URL' });
17+
}
18+
const history = await this.historyService.getAttemptedQuestions(userId);
19+
return res.status(200).json({ attemptedQuestionIds: history });
20+
} catch (error) {
21+
return res.status(500).json({ message: 'Server error' });
22+
}
23+
};
24+
25+
// Endpoint for Matching Service to POST a new attempt
26+
public addAttempt = async (req: Request, res: Response): Promise<Response> => {
27+
try {
28+
const { userId, questionId } = req.body;
29+
if (!userId || !questionId) {
30+
return res.status(400).json({ message: 'userId and questionId are required' });
31+
}
32+
await this.historyService.addQuestionAttempt(userId, questionId);
33+
return res.status(201).json({ message: 'History updated' });
34+
} catch (error) {
35+
return res.status(500).json({ message: 'Server error' });
36+
}
37+
};
38+
39+
// Updated endpoint to remove one or more questions from history
40+
public removeAttempts = async (req: Request, res: Response): Promise<Response> => {
41+
try {
42+
const userId = req.params['userId'];
43+
const { questionIds } = req.body; // Expect an array of question IDs in the body
44+
45+
if (!userId) {
46+
return res.status(400).json({ message: 'userId is required in the URL' });
47+
}
48+
if (!Array.isArray(questionIds) || questionIds.length === 0) {
49+
return res.status(400).json({ message: 'questionIds must be a non-empty array in the request body' });
50+
}
51+
52+
await this.historyService.removeQuestionAttempts(userId, questionIds);
53+
return res.status(200).json({ message: 'Questions removed from history' });
54+
} catch (error) {
55+
return res.status(500).json({ message: 'Server error' });
56+
}
57+
};
58+
59+
// New endpoint for users to clear their entire history
60+
public clearUserHistory = async (req: Request, res: Response): Promise<Response> => {
61+
try {
62+
const userId = req.params['userId'];
63+
if (!userId) {
64+
return res.status(400).json({ message: 'userId is required in the URL' });
65+
}
66+
await this.historyService.clearHistory(userId);
67+
return res.status(200).json({ message: 'History cleared for user' });
68+
} catch (error) {
69+
return res.status(500).json({ message: 'Server error' });
70+
}
71+
};
72+
}
73+

history-service/src/index.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import express from 'express';
2+
import cors from 'cors';
3+
import mongoose from 'mongoose';
4+
import { HistoryController } from './controllers/history.controller';
5+
6+
const app = express();
7+
const port = process.env['PORT'] || 8085;
8+
const mongoUri = process.env['MONGO_URI'];
9+
10+
// Middleware
11+
app.use(cors());
12+
app.use(express.json());
13+
14+
// Routes
15+
// This assumes you will mount your router on /api in this file
16+
// If not, adjust Task 4 URL accordingly.
17+
const historyController = new HistoryController();
18+
app.get('/api/history/:userId', historyController.getUserHistory);
19+
app.post('/api/history', historyController.addAttempt);
20+
app.delete('/api/history/:userId/questions', historyController.removeAttempts); // Route to remove one or more questions
21+
app.delete('/api/history/:userId', historyController.clearUserHistory); // New route to clear all history
22+
23+
// Connect to MongoDB
24+
if (!mongoUri) {
25+
console.error("FATAL ERROR: MONGO_URI is not defined.");
26+
process.exit(1);
27+
}
28+
29+
mongoose.connect(mongoUri)
30+
.then(() => {
31+
console.log('Connected to MongoDB');
32+
app.listen(port, () => {
33+
console.log(`History service running on port ${port}`);
34+
});
35+
})
36+
.catch(err => {
37+
console.error('Database connection error', err);
38+
process.exit(1);
39+
});
40+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Schema, model, Document } from 'mongoose';
2+
3+
interface IHistory extends Document {
4+
userId: string;
5+
attemptedQuestionIds: string[];
6+
}
7+
8+
const HistorySchema = new Schema<IHistory>({
9+
userId: {
10+
type: String,
11+
required: true,
12+
unique: true,
13+
index: true
14+
},
15+
attemptedQuestionIds: {
16+
type: [String],
17+
default: []
18+
},
19+
});
20+
21+
export const HistoryModel = model<IHistory>('History', HistorySchema, 'userHistories');
22+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { HistoryModel } from '../models/history.model';
2+
3+
export class HistoryService {
4+
5+
public async getAttemptedQuestions(userId: string): Promise<string[]> {
6+
const history = await HistoryModel.findOne({ userId: userId });
7+
return history ? history.attemptedQuestionIds : [];
8+
}
9+
10+
// This is the "log on assignment" function
11+
public async addQuestionAttempt(userId: string, questionId: string): Promise<void> {
12+
await HistoryModel.updateOne(
13+
{ userId: userId },
14+
{ $push: { attemptedQuestionIds: questionId } },
15+
{ upsert: true } // Creates a new document if one doesn't exist
16+
);
17+
}
18+
19+
// Function to remove one or more questions from history
20+
public async removeQuestionAttempts(userId: string, questionIds: string[]): Promise<void> {
21+
if (!Array.isArray(questionIds) || questionIds.length === 0) {
22+
return; // Still good to have this check
23+
}
24+
await HistoryModel.updateOne(
25+
{ userId: userId },
26+
{ $pull: { attemptedQuestionIds: { $in: questionIds } } }
27+
);
28+
}
29+
30+
// New function to clear all history for a user
31+
public async clearHistory(userId: string): Promise<void> {
32+
await HistoryModel.updateOne(
33+
{ userId: userId },
34+
{ $set: { attemptedQuestionIds: [] } } // Set the array to empty
35+
);
36+
}
37+
}
38+

history-service/tsconfig.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"compilerOptions": {
3+
"rootDir": "./src",
4+
"outDir": "./dist",
5+
"module": "commonjs",
6+
"target": "esnext",
7+
"lib": ["esnext"],
8+
"types": ["node"],
9+
"sourceMap": true,
10+
"declaration": true,
11+
"declarationMap": true,
12+
"noUncheckedIndexedAccess": true,
13+
"exactOptionalPropertyTypes": true,
14+
"noImplicitReturns": true,
15+
"noImplicitOverride": true,
16+
"noUnusedLocals": true,
17+
"noUnusedParameters": true,
18+
"noFallthroughCasesInSwitch": true,
19+
"noPropertyAccessFromIndexSignature": true,
20+
"strict": true,
21+
"isolatedModules": true,
22+
"moduleDetection": "force",
23+
"skipLibCheck": true,
24+
"esModuleInterop": true, // Added for better interop between CommonJS and ES Modules
25+
"allowSyntheticDefaultImports": true // Often needed with esModuleInterop
26+
},
27+
"include": ["src/**/*"],
28+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
29+
}

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,25 @@
77
"collaboration-service",
88
"matching-service",
99
"question-service",
10-
"user-service"
10+
"user-service",
11+
"history-service"
1112
],
1213
"scripts": {
13-
"dev": "concurrently --names front,collab,match,question,user --pad-prefix \"npm run dev:frontend\" \"npm run dev:collab\" \"npm run dev:match\" \"npm run dev:question\" \"npm run dev:user\"",
14+
"dev": "concurrently --names front,collab,match,question,user --pad-prefix \"npm run dev:frontend\" \"npm run dev:collab\" \"npm run dev:match\" \"npm run dev:question\" \"npm run dev:user\" \"npm run dev:history\"",
1415
"dev:frontend": "npm run dev -w frontend",
1516
"dev:collab": "npm run dev -w collaboration-service",
1617
"dev:match": "npm run dev -w matching-service",
1718
"dev:question": "npm run dev -w question-service",
1819
"dev:user": "npm run dev -w user-service",
20+
"dev:history": "npm run dev -w history-service",
1921
"build": "npm run build --workspaces --if-present",
2022
"lint": "concurrently \"npm run lint:frontend\" \"npm run lint:backend\"",
2123
"lint:fix": "concurrently \"npm run lint:frontend:fix\" \"npm run lint:backend:fix\"",
2224
"lint:frontend": "npm run lint --prefix frontend",
2325
"lint:frontend:fix": "npm run lint:fix --prefix frontend",
2426
"lint:user:fix": "eslint user-service --ext .ts --fix",
25-
"lint:backend": "eslint collaboration-service matching-service question-service user-service --ext .ts",
26-
"lint:backend:fix": "eslint collaboration-service matching-service question-service user-service --ext .ts --fix",
27+
"lint:backend": "eslint collaboration-service matching-service question-service user-service history-service --ext .ts",
28+
"lint:backend:fix": "eslint collaboration-service matching-service question-service user-service history-service --ext .ts --fix",
2729
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,md}\"",
2830
"test": "jest"
2931
},

0 commit comments

Comments
 (0)