Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#53 - Question fetcher finished #58

Merged
merged 11 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ services:
- "8003:8003"
networks:
- mynetwork

gameservice:
container_name: gameservice-wichat_en2b
image: ghcr.io/arquisoft/wichat_en2b/gameservice:latest
profiles: ["dev", "prod"]
build: ./gameservice
ports:
- "8004:8004"
networks:
- mynetwork
environment:
MONGODB_URI: mongodb://mongodb:27017/game

gatewayservice:
container_name: gatewayservice-wichat_en2b
Expand Down
1 change: 1 addition & 0 deletions gameservice/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
20 changes: 20 additions & 0 deletions gameservice/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Use the official Node.js image as the base image
FROM node:22

# Set the working directory inside the container
WORKDIR /usr/src/gameservice

# Copy package.json and package-lock.json to the working directory
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy the rest of the application code to the working directory
COPY . .

# Expose the port the app runs on
EXPOSE 8004

# Define the command to run the application
CMD ["node", "gameserver.js"]
3 changes: 2 additions & 1 deletion gameservice/gameserver.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const express = require('express');
const mongoose = require('mongoose');
const path = require('path');
const questionRouter = require('./routers/RouterQuestionRetriever');
const generateRouter = require('./routers/RouterQuestionFetcher');

Expand All @@ -13,7 +14,7 @@ mongoose.connect(mongoUri)
.catch(err => console.error('❌ Error when connecting to MongoDB:', err));

// Middleware to serve static files
app.use(express.static('public'));
app.use(express.static(path.join(__dirname, 'public')));

// Routers
app.use(questionRouter);
Expand Down
102 changes: 102 additions & 0 deletions gameservice/openapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
openapi: 3.0.0
info:
title: Wichat Game Service API
version: 1.0.0
description: API documentation for the Wichat Game Service
servers:
- url: http://localhost:8004
paths:
/game:
get:
summary: Retrieve random questions for the game
tags:
- Questions
responses:
'200':
description: A list of formatted questions
content:
application/json:
schema:
type: array
items:
type: object
properties:
image_name:
type: string
description: The image path for the question
answers:
type: array
items:
type: string
description: List of possible answers
right_answer:
type: string
description: The correct answer
'400':
description: Not enough questions in the database
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: No hay suficientes preguntas en la base de datos
'500':
description: Error retrieving questions
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: Error retrieving questions
/generate/{type}/{amount}:
get:
summary: Generate questions from Wikidata
tags:
- Questions
parameters:
- in: path
name: type
schema:
type: string
required: true
description: The type of items to fetch from Wikidata
- in: path
name: amount
schema:
type: integer
required: true
description: The number of items to fetch
responses:
'200':
description: Data fetched successfully
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: ✅ Data fetched successfully
'500':
description: Failed to retrieve data
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: ❌ Failed to retrieve data
components:
schemas:
Question:
type: object
properties:
subject:
type: string
answer:
type: string
5 changes: 2 additions & 3 deletions gameservice/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
"name": "gameservice",
"version": "1.0.0",
"description": "This is the service in charge of managing the logic of the game",
"main": "index.js",
"scripts": {
"start": "node gameserver.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.21.2",
"mongoose": "^8.10.1",
"node-fetch": "^3.3.2"
"mongoose": "^8.10.1"
}
}
29 changes: 21 additions & 8 deletions gameservice/routers/RouterQuestionFetcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,56 @@ const router = express.Router();
const wikiDataUri = "https://query.wikidata.org/sparql?format=json&query=";

router.get('/generate/:type/:amount', async (req, res) => {
// Query for fetching items of a specific type from Wikidata
var itemType = req.params['type'];
var amount = req.params['amount'];
const query = `
SELECT ?item ?itemLabel ?image WHERE {
?item wdt:P31/wdt:P279* wd:${itemType}. # Item type or subclass of item type
?item wdt:P31 wd:${itemType}. # Item type or subclass of item type
?item wdt:P18 ?image. # Item image (compulsory)
SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
LIMIT ${amount}
`;

try {
// Fetch data from Wikidata
const url = wikiDataUri + encodeURIComponent(query);
const response = await fetch(wikiDataUri + encodeURIComponent(query), {
headers: {
'User-Agent': 'wichat_en2b/1.0'
}
});


// Parse JSON response
const data = await response.json();

const items = data.results.bindings.filter(item => item.itemLabel.value != null && item.image.value != null
&& item.item.value.split('/')[item.item.value.split('/').size - 1] != item.itemLabel.value)
.map(item => ({
// Filter out items with missing data
const items = data.results.bindings.filter(item => {
const itemId = item.item.value.split('/').pop();
return item.itemLabel.value != null && item.image.value != null
&& !/^Q\d+$/.test(item.itemLabel.value) && itemId != item.itemLabel.value;
}).map(item => ({
name: item.itemLabel.value,
image: item.image.value
}));

await saveQuestionstoDB(items, itemType);
return res.status(200).json({ message: '✅ Data fetched successfully' });
// Save items to database and images to disk
await saveQuestionsToDB(items, itemType);

return res.status(200).json({ message: '✅ Data fetched successfully' });
} catch (error) {
console.error('❌ Error fetching data:', error);
return res.status(500).json({ error: '❌ Failed to retrieve data' });
}
});

async function saveQuestionstoDB(items, code) {
async function saveQuestionsToDB(items, code) {
// Ensure the images directory exists
const imagesDir = './public/images/';
if (!fs.existsSync(imagesDir)) {
fs.mkdirSync(imagesDir, { recursive: true });
}
try {
for (const item of items) {
var question = await Question.findOne({ subject: code, answer: item.name });
Expand All @@ -61,6 +73,7 @@ async function saveQuestionstoDB(items, code) {

await question.save();

// Save image to disk
const imageResponse = await fetch(item.image);
const arrayBuffer = await imageResponse.arrayBuffer();
const imageBuffer = Buffer.from(arrayBuffer);
Expand Down