La empresa Norsewind Studios ha decidido migrar su base de datos relacional a una base de datos no relacional utilizando MongoDB. Este cambio busca mejorar la capacidad de manejo de datos debido al creciente número de jugadores de su videojuego "Jotun’s Lair". La base de datos contiene información sobre los monstruos, el loot del juego, las mazmorras y los comentarios de los usuarios.
El objetivo principal de esta primera parte del proyecto es implementar las funciones necesarias para interactuar con la base de datos MongoDB desde Python, cubriendo todos los endpoints definidos en la API REST de la empresa. Estas funciones deben ser capaces de realizar operaciones de creación, lectura, actualización y eliminación (CRUD) sobre las colecciones relevantes: loot
, monsters
, rooms
, users
.
Las colecciones principales en la base de datos MongoDB son:
- Loot: Contiene información sobre los objetos del juego.
- Monsters: Contiene información sobre los monstruos del juego.
- Rooms: Contiene información sobre las habitaciones y sus conexiones, monstruos y loot presentes.
- Users: Contiene información sobre los usuarios y sus comentarios.
Se pueden encontrar en la carpeta queries_folder
.
-
get_loot(db)
:- Esta función recupera todos los objetos de loot del juego. Devuelve una lista de diccionarios con los campos
id
yname
.
- Esta función recupera todos los objetos de loot del juego. Devuelve una lista de diccionarios con los campos
-
get_loot_by_id(db, loot_id)
:- Recupera un objeto de loot específico identificado por su
loot_id
. Omite el campoamount
en la información dein_rooms
.
- Recupera un objeto de loot específico identificado por su
-
get_monster(db)
:- Recupera todos los monstruos del juego, devolviendo una lista de diccionarios con los campos
id
,name
,level
ytype
.
- Recupera todos los monstruos del juego, devolviendo una lista de diccionarios con los campos
-
get_monster_by_id(db, monster_id)
:- Recupera un monstruo específico identificado por su
monster_id
. Omite el campoamount
en la información dein_rooms
.
- Recupera un monstruo específico identificado por su
-
get_dungeons(db)
:- Recupera todas las mazmorras del juego utilizando un pipeline de agregación para agrupar los datos por
dungeon_id
ydungeon_name
.
- Recupera todas las mazmorras del juego utilizando un pipeline de agregación para agrupar los datos por
-
get_dungeon_by_id(db, dungeon_id)
:- Recupera información detallada sobre una mazmorra específica, incluyendo las habitaciones, conexiones entre habitaciones, monstruos y loot presentes, así como la cantidad de comentarios de cada categoría en cada habitación. Utiliza un pipeline de agregación y una función en Python para eliminar duplicados.
-
get_room_by_id(db, room_id)
:- Recupera información detallada sobre una habitación específica, incluyendo los monstruos, loot y comentarios asociados.
-
get_user(db)
:- Recupera información básica de todos los usuarios, incluyendo
email
,user_name
ycountry
.
- Recupera información básica de todos los usuarios, incluyendo
-
get_user_by_email(db, email)
:- Recupera información detallada sobre un usuario específico, identificado por su
email
.
- Recupera información detallada sobre un usuario específico, identificado por su
-
post_comment(db, user_email, room_id, text, category)
:- Añade un nuevo comentario a una habitación específica y lo asocia a un usuario. Verifica la existencia del usuario y la habitación antes de proceder.
-
post_monster(db, name, type, level, place, exp, man_page)
:- Añade un nuevo monstruo al juego con la información proporcionada.
-
post_loot(db, name, type1, type2, weight, gold)
:- Añade un nuevo objeto de loot al juego con la información proporcionada.
-
post_room(db, dungeon_id, dungeon_name, dungeon_lore, room_name, rooms_connected, inWP=None, outWP=None)
:- Añade una nueva habitación a una mazmorra, actualizando las conexiones con otras habitaciones.
-
put_room_monsters(db, room_id, monsters)
:- Actualiza la lista de monstruos en una habitación específica. Verifica la existencia de los monstruos antes de actualizar.
-
put_room_loot(db, room_id, loot)
:- Actualiza la lista de objetos de loot en una habitación específica. Verifica la existencia de los objetos de loot antes de actualizar.
-
put_room_connections(db, room_id, connections)
:- Actualiza las conexiones de una habitación con otras habitaciones. Verifica la existencia de las habitaciones antes de actualizar.
-
delete_room(db, room_id)
:- Elimina una habitación específica y remueve las referencias a ella de los monstruos y objetos de loot.
-
delete_monster(db, monster_id)
:- Elimina un monstruo específico y remueve las referencias a él de las habitaciones.
-
delete_loot(db, loot_id)
:- Elimina un objeto de loot específico y remueve las referencias a él de las habitaciones.
ej-1A. Haz una consulta que obtengan los datos necesarios para la colección y exporta el resultado a un fichero .json. Debajo puedes encontrar la estructura de la colección.
db.rooms.aggregate([
{
$unwind: "$hints"
},
{
$project: {
_id: 0,
Creation_date: "$hints.creation_date",
HintText: "$hints.hintText",
Category: "$hints.category",
References_room: {
IdR: "$room_id",
Name: "$room_name",
IdD: "$dungeon_id",
Dungeon: "$dungeon_name"
},
Publish_by: {
Email: "$hints.publish_by.email",
User_name: "$hints.publish_by.user_name",
CreationDate: "$hints.publish_by.creation_date",
Country: "$hints.publish_by.country"
}
}
},
{
$out: "Hints"
}
])
Si bien no estamos primero creando un json y luego subiendo ese json a la coleccion nueva, estamos creando directamente la colección en el script, desde donde ya se puede descargar el json. Esto es debido a que la única manera que hemos encontrado de generar el json primero era guardando el script en un fichero a parte y ejecutarlo, pero como tenemos la base de datos dentro de un contenedor es mucho lio.
ej-1B. A continuación, crea una nueva colección llamada Hints y usa el fichero .json para poblarla. Además, elimina el campo hints de las colecciones Rooms y User.
db.rooms.updateMany(
{}, // Filtro vacío para pillar todos los documentos
{
$unset: { "hints": "" } // Operación para eliminar el campo
}
)
db.users.updateMany(
{},
{
$unset: { "hints": "" }
}
)
ej-1C. Por último, actualiza las funciones de los endpoints: POST /comment, GET /dungeon/{dungeon_id} , GET /room/{room_id} y GET /user/{email}. ¿Como se ven afectados estos endpoints?
Ver el archivo ej-1c.ipynb
db.users.aggregate([
{
$group: {
_id: {
year: { $substr: ["$creation_date", 0, 4] },
country: "$country"
},
count: { $sum: 1 }
}
},
{
$group: {
_id: "$_id.year",
countries: {
$push: {
country: "$_id.country",
count: "$count"
}
}
}
},
{
$project: {
_id: 0,
year: "$_id",
countries: "$countries"
}
}
])
ej-2B. Los 20 países cuyos usuarios han realizado el mayor número de posts de tipo Lore en los últimos 5 años. Los países deben aparecen ordenados de mayor a menor número de posts.
db.Hints.aggregate([
{
$addFields: {
year: { $substr: ["$Creation_date", 0, 4] }
}
},
{
$set: {
year: { $toInt: "$year" }
}
},
{
$match: {
Category: { $eq: "lore" },
year: { $gte: 2018 }
}
},
{
$group: {
_id: "$Publish_by.Country",
count: { $sum: 1 }
}
},
{
$sort: { count: -1 }
},
{
$limit: 20
},
{
$project: {
_id: 0,
country: "$_id",
lore_posts: "$count"
}
}
])
ej-2C. Los 5 usuarios que más bugs han reportado en 2022. Deben aparecer ordenados de mayor a menor.
db.Hints.aggregate([
{
$match: {
Category: { $eq: "bug" },
Creation_date: { $regex: "^2022" }
}
},
{
$group: {
_id: "$Publish_by.User_name",
count: { $sum: 1 }
}
},
{
$sort: { count: -1 }
},
{
$limit: 5
},
{
$project: {
_id: 0,
user_name: "$_id",
bugs_reported: "$count"
}
}
])
Como hemos visto que los resultados más altos son personas con 2 mensajes, hemos comprobado cuantas personas en total, por separado, hay que han hecho comentarios de bugs en 2022 y luego cuantas personas nos ha salido del aggregate: que han sido 226 y 223 respectivamente y, efectivamente, cuando hacemos el aggregate solo hay 3 personas con más de 1 comentario sobre bugs.
En este caso la solución se basa en hacer el maximo de cada uno de los paises por separado. En un script se incluiría este y se cambiaria en "Publish_by.Country": {$eq: "es_ES"} la etiqueta de país por la del que corresponda.
db.Hints.aggregate([
{
$match: {
Category: { $eq: "suggestion" },
"Publish_by.Country": { $eq: "es_ES" }
}
},
{
$group: {
_id: "$References_room.Dungeon",
count: { $sum: 1 }
}
},
{
$group: {
_id: null,
max_sugs: { $max: "$count" },
max_sugs_dung: {
$push: {
dungeon: "$_id",
count: "$count"
}
}
}
},
{ $unwind: "$max_sugs_dung" },
{
$project: {
_id: 0,
dungeon_name: "$max_sugs_dung.dungeon",
is_max: { $eq: ["$max_sugs_dung.count", "$max_sugs"] }
}
},
{
$match: {
is_max: { $eq: true }
}
}
])