Skip to content

Commit

Permalink
basic question-service added
Browse files Browse the repository at this point in the history
  • Loading branch information
angelalvaigle committed Nov 3, 2024
1 parent 68ddf85 commit a0936fe
Show file tree
Hide file tree
Showing 13 changed files with 351 additions and 1 deletion.
16 changes: 16 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ services:
environment:
MONGODB_URI: mongodb://mongodb:27017/userdb

questionservice:
container_name: questionservice-${teamname:-defaultASW}
image: ghcr.io/arquisoft/wiq_7/questionservice:latest
profiles: ['dev', 'prod']
build: ./questionservice
depends_on:
- mongodb
ports:
- '8003:8003'
networks:
- mynetwork
environment:
MONGODB_URI: mongodb://mongodb:27017/questiondb

gatewayservice:
container_name: gatewayservice-${teamname:-defaultASW}
image: ghcr.io/arquisoft/wiq_7/gatewayservice:latest
Expand All @@ -48,13 +62,15 @@ services:
- mongodb
- userservice
- authservice
- questionservice
ports:
- '8000:8000'
networks:
- mynetwork
environment:
AUTH_SERVICE_URL: http://authservice:8002
USER_SERVICE_URL: http://userservice:8001
QUESTION_SERVICE_URL: http://questionservice:8003

webapp:
container_name: webapp-${teamname:-defaultASW}
Expand Down
32 changes: 32 additions & 0 deletions gatewayservice/gateway-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const port = 8000;

const authServiceUrl = process.env.AUTH_SERVICE_URL || 'http://localhost:8002';
const userServiceUrl = process.env.USER_SERVICE_URL || 'http://localhost:8001';
const questionServiceUrl =
process.env.QUESTION_SERVICE_URL || 'http://localhost:8003';

app.use(cors());
app.use(express.json());
Expand Down Expand Up @@ -64,6 +66,36 @@ app.get('/users', async (req, res) => {
}
});

app.post('/addquestion', async (req, res) => {
try {
// Forward the add question request to the question generation service
const addQuestionResponse = await axios.post(
questionServiceUrl + '/addquestion',
req.body
);
res.json(addQuestionResponse.data);
} catch (error) {
res
.status(error.response.status)
.json({ error: error.response.data.error });
}
});

app.get('/questions', async (req, res) => {
try {
// Forward the get question request to the question asking service
const getQuestionResponse = await axios.get(
questionServiceUrl + '/questions',
req.body
);
res.json(getQuestionResponse.data);
} catch (error) {
res
.status(error.response.status)
.json({ error: error.response.data.error });
}
});

// Read the OpenAPI YAML file synchronously
openapiPath = './openapi.yaml';
if (fs.existsSync(openapiPath)) {
Expand Down
2 changes: 2 additions & 0 deletions questionservice/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
coverage
20 changes: 20 additions & 0 deletions questionservice/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Use an official Node.js runtime as a parent image
FROM node:20

# Set the working directory in the container
WORKDIR /usr/src/questionservice

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

# Install app dependencies
RUN npm install

# Copy the app source code to the working directory
COPY . .

# Expose the port the app runs on
EXPOSE 8003

# Define the command to run your app
CMD ["node", "question-service.js"]
31 changes: 31 additions & 0 deletions questionservice/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "questionservice",
"version": "1.0.0",
"description": "Question service, in charge of generating questions in the application",
"main": "service.js",
"scripts": {
"start": "node question-service.js",
"test": "jest"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Arquisoft/wiq_7.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/Arquisoft/wiq_7/issues"
},
"homepage": "https://github.com/Arquisoft/wiq_7#readme",
"dependencies": {
"bcrypt": "^5.1.1",
"body-parser": "^1.20.2",
"express": "^4.18.2",
"mongoose": "^8.0.4"
},
"devDependencies": {
"jest": "^29.7.0",
"mongodb-memory-server": "^9.1.5",
"supertest": "^6.3.4"
}
}
32 changes: 32 additions & 0 deletions questionservice/question-model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const mongoose = require('mongoose');

const questionSchema = new mongoose.Schema({
type: {
type: String,
required: true,
},
path: {
type: String,
required: true,
},
right: {
type: String,
required: true,
},
wrong1: {
type: String,
// required: true,
},
wrong2: {
type: String,
// required: true,
},
wrong3: {
type: String,
// required: true,
},
});

const Question = mongoose.model('Question', questionSchema);

module.exports = Question;
54 changes: 54 additions & 0 deletions questionservice/question-service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// question-service.js
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const Question = require('./question-model');

const app = express();
const port = 8003;

// Middleware to parse JSON in request body
app.use(bodyParser.json());

// Connect to MongoDB
const mongoUri =
process.env.MONGODB_URI || 'mongodb://localhost:27017/questiondb';
mongoose.connect(mongoUri);

app.post('/addquestion', async (req, res) => {
try {
const newQuestion = new Question({
type: req.body.type,
path: req.body.path,
right: req.body.right,
// wrong1: req.body.wrong1,
// wrong2: req.body.wrong2,
// wrong3: req.body.wrong3,
});
await newQuestion.save();
res.json(newQuestion);
} catch (error) {
res.status(400).json({ error: error.message });
}
});

app.get('/questions', async (req, res) => {
try {
const questions = await Question.find(); // Fetch all questions
res.json(questions);
} catch (error) {
res.status(500).json({ error: error.message });
}
});

const server = app.listen(port, () => {
console.log(`Question Service listening at http://localhost:${port}`);
});

// Listen for the 'close' event on the Express.js server
server.on('close', () => {
// Close the Mongoose connection
mongoose.connection.close();
});

module.exports = server;
20 changes: 20 additions & 0 deletions webapp/src/assets/wrappers/AddQuestionContainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import styled from 'styled-components';

const Wrapper = styled.section`
display: grid;
row-gap: 2rem;
@media (min-width: 768px) {
grid-template-columns: 1fr 1fr;
column-gap: 1rem;
}
@media (min-width: 1120px) {
grid-template-columns: 1fr 1fr 1fr;
}
.generateDb {
display: grid;
grid-template-columns: 1fr;
row-gap: 2rem;
}
`;
export default Wrapper;
95 changes: 95 additions & 0 deletions webapp/src/components/AddQuestionContainer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React, { useState } from 'react';
import axios from 'axios';
import Wrapper from '../assets/wrappers/AddQuestionContainer.js';
import { Snackbar } from '@mui/material';
import SPARQLQueryDispatcher from '../utils/SPARQLQueryDispatcher';
import artworksQuery from '../utils/artworksQuery';

const endpointUrl = 'https://query.wikidata.org/sparql';
const queryDispatcher = new SPARQLQueryDispatcher(endpointUrl);
const apiEndpoint =
process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000';

const AddQuestionContainer = () => {
const [error, setError] = useState('');
const [openSnackbar, setOpenSnackbar] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);

const addQuestion = async ({ type, path, right }) => {
try {
await axios.post(`${apiEndpoint}/addquestion`, { type, path, right });
setOpenSnackbar(true);
} catch (error) {
console.log(error);
setError(error.response?.data?.error);
}
};

const generateArtworks = async () => {
setIsSubmitting(true);
try {
const query = await queryDispatcher.query(artworksQuery);
// handle results
const bindings = query.results.bindings;
for (const result of bindings) {
// Accedemos a cada propiedad
const workLabel = result.workLabel.value; // Título de la obra
const creator = result.creatorLabel.value; // Nombre del creador
const imageUrl = result.image.value; // URL de la imagen
const sitelinks = result.sitelinks.value; // Número de sitelinks

// Muestra los resultados en la consola
if (!workLabel.startsWith('http') && !creator.startsWith('http')) {
await addQuestion({
type: 'artwork',
path: workLabel,
right: creator,
});
}
}
setOpenSnackbar(true);
} catch (error) {
console.log('error');
setError(error.response?.data?.error);
} finally {
setIsSubmitting(false);
}
};

const handleCloseSnackbar = () => {
setOpenSnackbar(false);
};

return (
<Wrapper>
<div className="generateDb">
<h4>Update "Por su obra..."</h4>
<button
type="submit"
className="btn btn-block"
onClick={generateArtworks}
disabled={isSubmitting} // Deshabilitar el botón mientras está enviando
>
{isSubmitting ? 'updating...' : 'update DB'}
</button>
</div>

<Snackbar
open={openSnackbar}
autoHideDuration={6000}
onClose={handleCloseSnackbar}
message="Questions generated successfully"
/>
{error && (
<Snackbar
open={!!error}
autoHideDuration={6000}
onClose={() => setError('')}
message={`Error: ${error}`}
/>
)}
</Wrapper>
);
};

export default AddQuestionContainer;
1 change: 1 addition & 0 deletions webapp/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export { default as FormRow } from './FormRow';
export { default as BigSidebar } from './BigSidebar';
export { default as SmallSidebar } from './SmallSidebar';
export { default as Navbar } from './Navbar';
export { default as AddQuestionContainer } from './AddQuestionContainer';
5 changes: 4 additions & 1 deletion webapp/src/pages/Admin.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { AddQuestionContainer } from '../components';

const Admin = () => {
return <h1>Admin Page</h1>;
return <AddQuestionContainer />;
};

export default Admin;
14 changes: 14 additions & 0 deletions webapp/src/utils/SPARQLQueryDispatcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class SPARQLQueryDispatcher {
constructor(endpoint) {
this.endpoint = endpoint;
}

query(sparqlQuery) {
const fullUrl = this.endpoint + '?query=' + encodeURIComponent(sparqlQuery);
const headers = { Accept: 'application/sparql-results+json' };

return fetch(fullUrl, { headers }).then((body) => body.json());
}
}

export default SPARQLQueryDispatcher;
Loading

0 comments on commit a0936fe

Please sign in to comment.