diff --git a/README.md b/README.md
index 4c4932a..ac76ab5 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@
- [🌺Special requests](#special-requests)
- [`Pokemong` by nickname](#pokemong-by-nickname)
- [`Pokemong` in date interval](#pokemong-in-date-interval)
+ - [🦚Aggregation pipeline](#aggregation-pipeline)
- [Prep steps](#prep-steps)
- [♨️Java version](#java-version)
- [🔐Database connection](#database-connection)
@@ -309,9 +310,33 @@ where `{nickname}` is a partial, case-insensitive search term.
#### `Pokemong` in date interval
-Users can also use the route `pokemong/dob/{startDate}/{endDate}` to search for
+Users can also use the route `pokemong/dob/{start-date}/{end-date}` to search for
`pokemongs` who where born within that interval (bounds included).
+### 🦚Aggregation pipeline
+
+Finally, the endpoint `pokemong/count-by-evo-stage` is provided, to get a mapping of evolution stages with
+the number of `pokemongs`who achieved that evolution stage.
+
+As an example of a potential output:
+
+```json
+[
+ {
+ "count": 15,
+ "evoStage": 0
+ },
+ {
+ "count": 4,
+ "evoStage": 1
+ },
+ {
+ "count": 5,
+ "evoStage": 2
+ }
+]
+```
+
## Prep steps
### ♨️Java version
@@ -376,11 +401,13 @@ You can run the application in dev mode using:
🏫 If you are the corrector
-Please navigate to the root of this project in a terminal and run the provided `load_data.sh` script.
+The database should already be populated with the sample dataset.
+
+However, if you want to reload that dataset, please navigate to the root of this project in a terminal and run
+the provided `load_data.sh` script.
If the script wasn't provided, that was a mistake. Sorry. Please request them to the owner of this repo, or follow the
-alternate
-procedure below.
+alternate procedure below.
diff --git a/data/sample-dataset/moves.json b/data/sample-dataset/moves.json
index 0bfac4f..4093c84 100644
--- a/data/sample-dataset/moves.json
+++ b/data/sample-dataset/moves.json
@@ -38,3 +38,59 @@
"effectiveAgainst": ["GRASS"]
}
}
+{
+ "_id": { "$oid":"60a64f7eae945a6e60b0e919" },
+ "schemaVersion": 2,
+ "name": "Twineedle",
+ "power": 25,
+ "pp": 20,
+ "category": "PHYSICAL",
+ "accuracy": 100,
+ "type": {
+ "name": "BUG",
+ "weakAgainst": ["FIRE", "FLYING"],
+ "effectiveAgainst": ["GRASS", "PSYCHIC"]
+ }
+}
+{
+ "_id": { "$oid":"60a64f7eae945a6e60b0e91a" },
+ "schemaVersion": 2,
+ "name": "Rage",
+ "power": 20,
+ "pp": 20,
+ "category": "PHYSICAL",
+ "accuracy": 100,
+ "type": {
+ "name": "NORMAL",
+ "weakAgainst": ["ROCK"],
+ "effectiveAgainst": []
+ }
+}
+{
+ "_id": { "$oid":"60a64f7eae945a6e60b0e91c" },
+ "schemaVersion": 2,
+ "name": "Gust",
+ "power": 40,
+ "pp": 35,
+ "category": "SPECIAL",
+ "accuracy": 100,
+ "type": {
+ "name": "FLYING",
+ "weakAgainst": ["ROCK", "ELECTRIC", "ICE"],
+ "effectiveAgainst": ["GRASS", "FIGHTING", "BUG"]
+ }
+}
+{
+ "_id": { "$oid":"60a64f7eae945a6e60b0e91d" },
+ "schemaVersion": 2,
+ "name": "Confusion",
+ "power": 50,
+ "pp": 25,
+ "category": "SPECIAL",
+ "accuracy": 100,
+ "type": {
+ "name": "PSYCHIC",
+ "weakAgainst": ["BUG", "GHOST", "DARK"],
+ "effectiveAgainst": ["FIGHTING", "POISON"]
+ }
+}
diff --git a/data/sample-dataset/pokemongs.json b/data/sample-dataset/pokemongs.json
index df4fb06..dd3c912 100644
--- a/data/sample-dataset/pokemongs.json
+++ b/data/sample-dataset/pokemongs.json
@@ -96,3 +96,97 @@
}
]
}
+{
+ "_id": {
+ "$oid": "60a64f7eae945a6e60b0e918"
+ },
+ "schemaVersion": 1,
+ "nickname": "Buzzy",
+ "dob": {
+ "$date": {
+ "$numberLong": "761597651000"
+ }
+ },
+ "level": 8,
+ "pokedexId": 15,
+ "evoStage": 2,
+ "evoTrack": [
+ "WEEDLE",
+ "KAKUNA",
+ "BEEDRILL"
+ ],
+ "types": [
+ {
+ "name": "BUG",
+ "weakAgainst": [
+ "FIRE",
+ "FLYING"
+ ],
+ "effectiveAgainst": [
+ "GRASS",
+ "PSYCHIC"
+ ]
+ }
+ ],
+ "moveSet": [
+ {
+ "_id": {
+ "$oid": "60a64f7eae945a6e60b0e919"
+ },
+ "name": "Twineedle"
+ },
+ {
+ "_id": {
+ "$oid": "60a64f7eae945a6e60b0e91a"
+ },
+ "name": "Rage"
+ }
+ ]
+}
+{
+ "_id": {
+ "$oid": "60a64f7eae945a6e60b0e91b"
+ },
+ "schemaVersion": 1,
+ "nickname": "Flutter",
+ "dob": {
+ "$date": {
+ "$numberLong": "861597551000"
+ }
+ },
+ "level": 12,
+ "pokedexId": 12,
+ "evoStage": 2,
+ "evoTrack": [
+ "CATERPIE",
+ "METAPOD",
+ "BUTTERFREE"
+ ],
+ "types": [
+ {
+ "name": "BUG",
+ "weakAgainst": [
+ "FIRE",
+ "FLYING"
+ ],
+ "effectiveAgainst": [
+ "GRASS",
+ "PSYCHIC"
+ ]
+ }
+ ],
+ "moveSet": [
+ {
+ "_id": {
+ "$oid": "60a64f7eae945a6e60b0e91c"
+ },
+ "name": "Gust"
+ },
+ {
+ "_id": {
+ "$oid": "60a64f7eae945a6e60b0e91d"
+ },
+ "name": "Confusion"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/main/java/fr/uca/iut/controllers/PokemongController.java b/src/main/java/fr/uca/iut/controllers/PokemongController.java
index a328a52..b125ed2 100644
--- a/src/main/java/fr/uca/iut/controllers/PokemongController.java
+++ b/src/main/java/fr/uca/iut/controllers/PokemongController.java
@@ -39,10 +39,10 @@ public class PokemongController extends GenericController {
}
@GET
- @Path("/dob/{startDate}/{endDate}")
+ @Path("/dob/{start-date}/{end-date}")
public Response findByDateOfBirthInterval(
- @PathParam("startDate") String startDate,
- @PathParam("endDate") String endDate
+ @PathParam("start-date") String startDate,
+ @PathParam("end-date") String endDate
) {
if (StringUtils.isBlankStringOrNull(startDate) || StringUtils.isBlankStringOrNull(endDate)) {
return Response.ok(new ArrayList<>())
@@ -59,4 +59,10 @@ public class PokemongController extends GenericController {
}
}
+ @GET
+ @Path("/count-by-evo-stage")
+ public Response countPokemongsByEvoStage() {
+ return Response.ok(pokemongService.countPokemongsByEvoStage())
+ .build();
+ }
}
diff --git a/src/main/java/fr/uca/iut/repositories/PokemongRepository.java b/src/main/java/fr/uca/iut/repositories/PokemongRepository.java
index 04c3851..409b29f 100644
--- a/src/main/java/fr/uca/iut/repositories/PokemongRepository.java
+++ b/src/main/java/fr/uca/iut/repositories/PokemongRepository.java
@@ -3,17 +3,23 @@ package fr.uca.iut.repositories;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.Accumulators;
+import com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.Projections;
import fr.uca.iut.entities.Pokemong;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
+import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
+import org.jetbrains.annotations.NotNull;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.List;
@@ -40,10 +46,15 @@ public class PokemongRepository extends GenericRepository {
@Override
protected MongoCollection getCollection() {
- MongoDatabase db = mongoClient.getDatabase(DB_NAME);
+ MongoDatabase db = getMongoDatabase();
return db.getCollection(Pokemong.COLLECTION_NAME, Pokemong.class);
}
+ @NotNull
+ private MongoDatabase getMongoDatabase() {
+ return mongoClient.getDatabase(DB_NAME);
+ }
+
/**
* Fetches the list of Pokemong entities that have a nickname matching the provided nickname.
* The match is case-insensitive and ignores leading and trailing spaces.
@@ -77,4 +88,28 @@ public class PokemongRepository extends GenericRepository {
.into(new ArrayList<>());
}
+ /**
+ * Counts the number of Pokemongs grouped by their evolution stage.
+ *
+ * @return A list of MongoDB Documents, each containing the evolution stage
+ * ('evoStage') and the count of Pokemongs at that stage.
+ */
+ public List countPokemongsByEvoStage() {
+ List pipeline = Arrays.asList(
+ Aggregates.group("$evoStage", Accumulators.sum("count", 1)),
+ // "evoStage" becomes "_id" here
+ Aggregates.project(Projections.fields(
+ Projections.excludeId(),
+ // we discard that "_id" field
+ Projections.computed("evoStage", "$_id"),
+ // but we add it back in after renaming it "evoStage"
+ Projections.include("count")
+ // and of course we still want to keep the count
+ ))
+ );
+
+ MongoCollection collection = getMongoDatabase().getCollection(getCollection().getNamespace().getCollectionName());
+ return collection.aggregate(pipeline, Document.class).into(new ArrayList<>());
+ }
+
}
diff --git a/src/main/java/fr/uca/iut/services/PokemongService.java b/src/main/java/fr/uca/iut/services/PokemongService.java
index 0178863..6106a81 100644
--- a/src/main/java/fr/uca/iut/services/PokemongService.java
+++ b/src/main/java/fr/uca/iut/services/PokemongService.java
@@ -13,6 +13,7 @@ import fr.uca.iut.utils.exceptions.NonValidEntityException;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
+import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import java.time.LocalDate;
@@ -208,10 +209,6 @@ public class PokemongService extends GenericService {
return repository.existsById(pokemongId);
}
- public List findByNickname(String nickname) {
- return pokemongRepository.findByNickname(nickname);
- }
-
public void batchUpdatePokemongTrainers(@NotNull Set trainerPokemongs,
@Nullable String trainerId) {
List pokemongsToUpdate = new ArrayList<>();
@@ -225,8 +222,17 @@ public class PokemongService extends GenericService {
updateAll(pokemongsToUpdate);
}
+ public List findByNickname(String nickname) {
+ return pokemongRepository.findByNickname(nickname);
+ }
+
public List findByDateOfBirthInterval(LocalDate startDate, LocalDate endDate) {
return pokemongRepository.findByDateOfBirthInterval(startDate, endDate);
}
+ public List countPokemongsByEvoStage() {
+ return pokemongRepository.countPokemongsByEvoStage();
+ }
+
+
}
diff --git a/src/main/resources/META-INF/openapi.yaml b/src/main/resources/META-INF/openapi.yaml
index 523e631..2094156 100644
--- a/src/main/resources/META-INF/openapi.yaml
+++ b/src/main/resources/META-INF/openapi.yaml
@@ -94,19 +94,19 @@ paths:
items:
$ref: '#/components/schemas/Pokemong'
- /pokemong/dob/{startDate}/{endDate}:
+ /pokemong/dob/{start-date}/{end-date}:
get:
summary: Get Pokemongs born within a certain date interval
parameters:
- - name: startDate
+ - name: start-date
in: path
required: true
description: The start of the date interval (inclusive)
schema:
type: string
format: date
- - name: endDate
+ - name: end-date
in: path
required: true
description: The end of the date interval (inclusive)
@@ -125,6 +125,20 @@ paths:
'400':
description: Invalid date format
+ /pokemong/count-by-evo-stage:
+
+ get:
+ summary: Get a mapping of each distinct evolution stage to the count of Pokemongs at that stage
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: object
+ additionalProperties:
+ type: integer
+
/pokemong:
get: