diff --git a/docker-compose.yml b/docker-compose.yml index ac8cd759..3d52f394 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/gameservice/.dockerignore b/gameservice/.dockerignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/gameservice/.dockerignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/gameservice/Dockerfile b/gameservice/Dockerfile new file mode 100644 index 00000000..c9f80e67 --- /dev/null +++ b/gameservice/Dockerfile @@ -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"] diff --git a/gameservice/gameserver.js b/gameservice/gameserver.js index 47c03c8c..639b29a3 100644 --- a/gameservice/gameserver.js +++ b/gameservice/gameserver.js @@ -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'); @@ -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); diff --git a/gameservice/openapi.yaml b/gameservice/openapi.yaml new file mode 100644 index 00000000..0835b213 --- /dev/null +++ b/gameservice/openapi.yaml @@ -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 diff --git a/gameservice/package.json b/gameservice/package.json index 7c444d45..3d37ebf5 100644 --- a/gameservice/package.json +++ b/gameservice/package.json @@ -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" } } diff --git a/gameservice/routers/RouterQuestionFetcher.js b/gameservice/routers/RouterQuestionFetcher.js index bfc1b427..dd8d708c 100644 --- a/gameservice/routers/RouterQuestionFetcher.js +++ b/gameservice/routers/RouterQuestionFetcher.js @@ -6,11 +6,12 @@ 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". } } @@ -18,32 +19,43 @@ router.get('/generate/:type/:amount', async (req, res) => { `; 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 }); @@ -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);