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: