diff --git a/.gitignore b/.gitignore index 8c967c2..afff217 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,6 @@ gradle-app.setting # Cache of project .gradletasknamecache + +# Others +docs/todos.md \ No newline at end of file diff --git a/README.md b/README.md index 158c85e..02ae1ec 100644 --- a/README.md +++ b/README.md @@ -83,10 +83,18 @@ You can run the application in dev mode using: ## API testing +### 🏴‍☠️ SwaggerUI + +Thanks to this project's OpenAPI specs, you can explore the API in a lot of ways. +A popular choice is SwaggerUI -- after you run the app, just go to http://localhost:8080/q/swagger-ui and have fun. + +⚠️ Unfortunately, Swagger or Quarkus or SmallRye adds the field `id` to all request examples, but in fact ***you should +not include id** when you POST a new document.* + ### 🩺 API testing tools -It is recommended to use an API testing tool such as [Postman](https://www.postman.com/) -or [Insomnia](https://insomnia.rest/), while playing around with this app. +You can use an API testing tool such as [Postman](https://www.postman.com/) +or [Insomnia](https://insomnia.rest/) to test this app. ### 📱 Front end (later) diff --git a/build.gradle b/build.gradle index 5091209..ae4479e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,43 +1,43 @@ -plugins { - id 'java' - id 'io.quarkus' -} - -repositories { - mavenCentral() - mavenLocal() -} - -dependencies { - // FIXME "Provides transitive vulnerable dependency maven:org.jboss.resteasy:resteasy-core:6.2.1.Final - // CVE-2023-0482 7.8 Creation of Temporary File With Insecure Permissions vulnerability with medium severity found - // Results powered by Checkmarx(c)" - implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") - implementation 'io.quarkus:quarkus-resteasy:3.0.0.Alpha6' - implementation 'io.quarkus:quarkus-resteasy-jackson:3.0.0.Alpha6' - implementation 'io.quarkus:quarkus-arc:3.0.0.Alpha6' - implementation 'io.quarkus:quarkus-mongodb-client:3.0.0.Alpha6' - implementation 'org.mongodb:mongodb-driver-sync:4.9.1' - testImplementation 'io.quarkus:quarkus-junit5:3.0.0.Alpha6' - testImplementation 'io.rest-assured:rest-assured:5.3.0' -} - -group 'fr.uca.iut' -version '1.0-SNAPSHOT' - -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 -} - -test { - systemProperty "java.util.logging.manager", "org.jboss.logmanager.LogManager" -} -compileJava { - options.encoding = 'UTF-8' - options.compilerArgs << '-parameters' -} - -compileTestJava { - options.encoding = 'UTF-8' -} +plugins { + id 'java' + id 'io.quarkus' +} + +repositories { + mavenCentral() + mavenLocal() +} + +dependencies { + implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") + implementation 'io.quarkus:quarkus-resteasy:3.0.0.Alpha6' + implementation 'io.quarkus:quarkus-resteasy-jackson:3.0.0.Alpha6' + implementation 'io.quarkus:quarkus-arc:3.0.0.Alpha6' + implementation 'io.quarkus:quarkus-mongodb-client:3.0.0.Alpha6' + implementation 'org.mongodb:mongodb-driver-sync:4.9.1' + implementation 'org.jetbrains:annotations:24.0.1' + implementation 'io.quarkus:quarkus-smallrye-openapi:2.16.4.Final' + implementation 'io.quarkus:quarkus-swagger-ui:2.16.4.Final' + testImplementation 'io.quarkus:quarkus-junit5:3.0.0.Alpha6' + testImplementation 'io.rest-assured:rest-assured:5.3.0' +} + +group 'fr.uca.iut' +version '1.0-SNAPSHOT' + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +test { + systemProperty "java.util.logging.manager", "org.jboss.logmanager.LogManager" +} +compileJava { + options.encoding = 'UTF-8' + options.compilerArgs << '-parameters' +} + +compileTestJava { + options.encoding = 'UTF-8' +} diff --git a/docs/DB.md b/docs/DB.md index ce5bce5..f7dfcb2 100644 --- a/docs/DB.md +++ b/docs/DB.md @@ -6,11 +6,11 @@ * _id: ObjectId * name: string - * (_indexed_: would be queried often in a dashboard situation) + * (_indexed_: would often be queried in a dashboard situation) * dob: date * wins: int * losses: int -* past_opponents: array of ObjectId (references to other trainers) +* pastOpponents: array of ObjectId (references to other trainers) * (_indexed_: reflexivity would make deep queries quite slow, so it seems worthwhile) * pokemongs: array of ObjectId (references to owned pokemongs) + denormalizing on "nickname" and "species" * (_indexed_: to improve speed when querying non-denormalized fields) @@ -18,58 +18,70 @@ ### pokemongs collection * _id: ObjectId -* nickname: string +* nickname: string? * dob: date * level: int -* pokedex_id: int -* evo_stage: int - * (_indexed_: "species" is calculated as evo_track[evo_stage], and would be queried often) -* evo_track: array of strings (therefore "species" is evo_track[evo_stage], and "evo_base" is evo_track[0]) - * (_indexed_: "species" is calculated as evo_track[evo_stage], and xould be queried often) -* is_mega_evolved: boolean - * **polymorphic**: this field is only here for mature_pokemongs, i.e. pokemongs who have reached their last evo_stage +* pokedexId: int +* evoStage: int + * (_indexed_: "species" is calculated as evoTrack[evoStage], and would often be queried) +* evoTrack: array of strings (therefore "species" is evoTrack[evoStage], and "evoBase" is evoTrack[0]) + * (_indexed_: "species" is calculated as evoTrack[evoStage], and would be queried often) * trainer: ObjectId? (reference to a trainer) (but can be "wild" instead, if ref is null) * (_indexed_: could be queried often in a dashboard situation) * types: embedded type, or array of embedded types - * (_indexed_: would be queried often in a dashboard situation) -* move_set: array of ObjectId (references to known moves) + denormalizing on "name" + * (_indexed_: would often be queried in a dashboard situation) +* moveSet: array of ObjectId (references to known moves) + denormalizing on "name" ### moves collection * _id: ObjectId * name: string - * (_indexed_: would be queried often in a dashboard situation) + * (_indexed_: would often be queried in a dashboard situation) * category: string (can be "physical", "special", or "status") -* pp: int * power: int - * (_indexed_: would be used often in sorts, in a dashboard situation) + * (_indexed_: would often be used in sorts, in a dashboard situation) * accuracy: int * type: embedded type - * (_indexed_: would be queried often in a dashboard situation) - + * (_indexed_: would often be queried in a dashboard situation) ### types collection * _id: ObjectId * name: string - * (_indexed_: would be queried often in a dashboard situation) -* weak_against: array of strings (denormalized type names) -* effective_against: array of strings (denormalized type names) + * (_indexed_: would often be queried in a dashboard situation) +* weakAgainst: array of strings (denormalized type names) +* effectiveAgainst: array of strings (denormalized type names) ## Relationships -* trainers.past_opponents: one-to-many and reflexive - * => referencing -* trainers.pokemongs: one-to-many - * => referencing + denormalizing on "nickname" and "species" -* pokemongs.trainer: many-to-one - * => referencing -* pokemongs.types: one-to-few [1;2] but will also need to be queried independently - * => denormalizing on all fields -* pokemongs.move_set: one-to-few [1;4] but will also need to be queried independently - * => referencing + denormalizing on "name" -* moves.type: one-to-one [1;1] but will also need to be queried independently - * => denormalizing on all fields -* types.weak_against & types.effective_against: one-to-few but reflexive - * => denormalizing on "name" - \ No newline at end of file +- Trainer + - [x] trainers.pastOpponents: one-to-many and reflexive + * => referencing + - [x] trainers.pokemongs: one-to-many + * => referencing + denormalizing on "nickname" and "species" +- Pokemong + - [x] pokemongs.trainer: many-to-one + * => referencing + - [x] pokemongs.types: one-to-few [1;2] + * => embedding + - [x] pokemongs.moveSet: one-to-few [1;4] but will also need to be queried independently + * => referencing + denormalizing on "name" +- Move + - [x] moves.type: one-to-one [1;1] + * => embedding +- Type + - [x] types.weakAgainst & types.effectiveAgainst: one-to-few, but reflexive + * => denormalizing on "name" + +## Cascades + +- Pokemong + - [x] delete ~> trainer.pokemongs + - [x] update ~> trainer.pokemongs (denormalizing on "nickname" and "species") + - [x] create ~> trainer.pokemongs +- Trainer + - [x] delete ~> pokemong.trainer + - [x] create ~> pokemong.trainer +- Move + - [x] delete ~> pokemong.moveSet + - [x] update ~> pokemong.moveSet (denormalizing on "name") diff --git a/docs/mcd.png b/docs/mcd.png index a8ef40a..20e3532 100644 Binary files a/docs/mcd.png and b/docs/mcd.png differ diff --git a/docs/nosql_uml.png b/docs/nosql_uml.png index 0979200..e32ec6f 100644 Binary files a/docs/nosql_uml.png and b/docs/nosql_uml.png differ diff --git a/src/main/java/fr/uca/iut/codecs/GenericCodec.java b/src/main/java/fr/uca/iut/codecs/GenericCodec.java index c948176..25faad3 100644 --- a/src/main/java/fr/uca/iut/codecs/GenericCodec.java +++ b/src/main/java/fr/uca/iut/codecs/GenericCodec.java @@ -1,48 +1,41 @@ -package fr.uca.iut.codecs; - -import com.mongodb.MongoClientSettings; -import fr.uca.iut.entities.GenericEntity; -import org.bson.*; -import org.bson.codecs.Codec; -import org.bson.codecs.CollectibleCodec; -import org.bson.codecs.DecoderContext; -import org.bson.codecs.EncoderContext; -import org.bson.types.ObjectId; - -public abstract class GenericCodec implements CollectibleCodec { - private final Codec documentCodec; - protected GenericCodec() { - this.documentCodec = MongoClientSettings.getDefaultCodecRegistry() - .get(Document.class); - } - - public Codec getDocumentCodec() { - return documentCodec; - } - @Override - public abstract void encode(BsonWriter writer, T entity, EncoderContext encoderContext); - - @Override - public abstract Class getEncoderClass(); - - @Override - public T generateIdIfAbsentFromDocument(T document) { - if (!documentHasId(document)) { - document.setId(new ObjectId().toString()); - } - return document; - } - - @Override - public boolean documentHasId(T document) { - return document.getId() != null; - } - - @Override - public BsonValue getDocumentId(T document) { - return new BsonObjectId(new ObjectId(document.getId())); - } - - @Override - public abstract T decode(BsonReader reader, DecoderContext decoderContext); -} +package fr.uca.iut.codecs; + +import fr.uca.iut.entities.GenericEntity; +import org.bson.BsonObjectId; +import org.bson.BsonReader; +import org.bson.BsonValue; +import org.bson.BsonWriter; +import org.bson.codecs.CollectibleCodec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.types.ObjectId; + +public abstract class GenericCodec implements CollectibleCodec { + + @Override + public abstract void encode(BsonWriter writer, T entity, EncoderContext encoderContext); + + @Override + public abstract Class getEncoderClass(); + + @Override + public T generateIdIfAbsentFromDocument(T document) { + if (!documentHasId(document)) { + document.setId(new ObjectId().toString()); + } + return document; + } + + @Override + public boolean documentHasId(T document) { + return document.getId() != null; + } + + @Override + public BsonValue getDocumentId(T document) { + return new BsonObjectId(new ObjectId(document.getId())); + } + + @Override + public abstract T decode(BsonReader reader, DecoderContext decoderContext); +} diff --git a/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java b/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java new file mode 100644 index 0000000..13b7ec8 --- /dev/null +++ b/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java @@ -0,0 +1,78 @@ +package fr.uca.iut.codecs.move; + +import com.mongodb.MongoClientSettings; +import fr.uca.iut.codecs.GenericCodec; +import fr.uca.iut.codecs.type.TypeCodecUtil; +import fr.uca.iut.entities.Move; +import fr.uca.iut.entities.Type; +import fr.uca.iut.utils.enums.MoveCategoryName; +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.Document; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.types.ObjectId; + +public class MoveCodec extends GenericCodec { + private final Codec documentCodec; + + public MoveCodec() { + this.documentCodec = MongoClientSettings.getDefaultCodecRegistry() + .get(Document.class); + } + + @Override + public void encode(BsonWriter writer, Move move, EncoderContext encoderContext) { + Document doc = new Document(); + + doc.put("_id", new ObjectId(move.getId())); + + doc.put("name", move.getName()); + + doc.put("category", move.getCategory()); + + doc.put("power", move.getPower()); + + doc.put("accuracy", move.getAccuracy()); + + Type moveType = move.getType(); + Document typeDoc = new Document(); + typeDoc.put("name", + moveType.getName() + .toString()); + typeDoc.put("weakAgainst", moveType.getWeakAgainst()); + typeDoc.put("effectiveAgainst", moveType.getEffectiveAgainst()); + doc.put("type", typeDoc); + + documentCodec.encode(writer, doc, encoderContext); + } + + @Override + public Class getEncoderClass() { + return Move.class; + } + + @Override + public Move decode(BsonReader reader, DecoderContext decoderContext) { + Document document = documentCodec.decode(reader, decoderContext); + Move move = new Move(); + + move.setId(document.getObjectId("_id") + .toString()); + + move.setName(document.getString("name")); + + move.setCategory(MoveCategoryName.valueOf(document.getString("category"))); + + move.setPower(document.getInteger("power")); + + move.setAccuracy(document.getInteger("accuracy")); + + Document typeDoc = (Document) document.get("type"); + + move.setType(TypeCodecUtil.extractType(typeDoc)); + + return move; + } +} diff --git a/src/main/java/fr/uca/iut/codecs/move/MoveCodecProvider.java b/src/main/java/fr/uca/iut/codecs/move/MoveCodecProvider.java new file mode 100644 index 0000000..312afd9 --- /dev/null +++ b/src/main/java/fr/uca/iut/codecs/move/MoveCodecProvider.java @@ -0,0 +1,18 @@ +package fr.uca.iut.codecs.move; + +import com.mongodb.lang.Nullable; +import fr.uca.iut.entities.Move; +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecProvider; +import org.bson.codecs.configuration.CodecRegistry; + +public class MoveCodecProvider implements CodecProvider { + @Nullable + @Override + public Codec get(Class clazz, CodecRegistry registry) { + if (clazz.equals(Move.class)) { + return (Codec) new MoveCodec(); + } + return null; + } +} diff --git a/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java b/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java index 0610934..de7c7fa 100644 --- a/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java +++ b/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java @@ -1,127 +1,153 @@ -package fr.uca.iut.codecs.pokemong; - -import com.mongodb.MongoClientSettings; -import fr.uca.iut.codecs.GenericCodec; -import fr.uca.iut.entities.Pokemong; -import fr.uca.iut.entities.Type; -import fr.uca.iut.utils.PokemongName; -import fr.uca.iut.utils.TypeName; -import org.bson.*; -import org.bson.codecs.*; -import org.bson.types.ObjectId; - -import java.time.ZoneId; -import java.util.*; -import java.util.stream.Collectors; - -public class PokemongCodec extends GenericCodec { - private final Codec documentCodec; - - public PokemongCodec() { - this.documentCodec = MongoClientSettings.getDefaultCodecRegistry() - .get(Document.class); - } - - @Override - public void encode(BsonWriter writer, Pokemong pokemong, EncoderContext encoderContext) { - Document doc = new Document(); - doc.put("_id", new ObjectId(pokemong.getId())); - doc.put("nickname", pokemong.getNickname()); - doc.put("dob", - Date.from(pokemong.getDob() - .atStartOfDay(ZoneId.systemDefault()) - .toInstant())); - doc.put("level", pokemong.getLevel()); - doc.put("pokedexId", pokemong.getPokedexId()); - doc.put("evoStage", pokemong.getEvoStage()); - List evoTrack = Optional.ofNullable(pokemong.getEvoTrack()) - .orElse(Collections.emptyList()) - .stream() - .map(Enum::name) - .collect(Collectors.toList()); - - doc.put("evoTrack", evoTrack); - doc.put("isMegaEvolved", pokemong.getMegaEvolved()); - doc.put("trainer", pokemong.getTrainer()); - List types = Optional.ofNullable(pokemong.getTypes()) - .orElse(Collections.emptyList()) - .stream() - .map(type -> { - Document typeDoc = new Document(); - typeDoc.put("name", - type.getName() - .name()); - List weakAgainst = type.getWeakAgainst() - .stream() - .map(Enum::name) - .collect(Collectors.toList()); - typeDoc.put("weakAgainst", weakAgainst); - List effectiveAgainst = type.getEffectiveAgainst() - .stream() - .map(Enum::name) - .collect(Collectors.toList()); - typeDoc.put("effectiveAgainst", effectiveAgainst); - return typeDoc; - }) - .collect(Collectors.toList()); - doc.put("types", types); - doc.put("moveSet", pokemong.getMoveSet()); - documentCodec.encode(writer, doc, encoderContext); - } - - @Override - public Class getEncoderClass() { - return Pokemong.class; - } - - @Override - public Pokemong decode(BsonReader reader, DecoderContext decoderContext) { - Document document = documentCodec.decode(reader, decoderContext); - Pokemong pokemong = new Pokemong(); - pokemong.setId(document.getObjectId("_id").toString()); - pokemong.setNickname(document.getString("nickname")); - Date dob = document.getDate("dob"); - if (dob != null) { - pokemong.setDob(dob.toInstant() - .atZone(ZoneId.systemDefault()) - .toLocalDate()); - } - pokemong.setPokedexId(document.getInteger("pokedexId")); - pokemong.setEvoStage(document.getInteger("evoStage")); - List evoTrack = Optional.ofNullable((List) document.get("evoTrack")) - .orElse(Collections.emptyList()) - .stream() - .map(PokemongName::valueOf) - .collect(Collectors.toList()); - pokemong.setEvoTrack(evoTrack); - pokemong.setMegaEvolved(document.getBoolean("isMegaEvolved")); - pokemong.setTrainer(document.getObjectId("trainer")); - List types = Optional.ofNullable((List) document.get("types")) - .orElse(Collections.emptyList()) - .stream() - .map(typeDoc -> { - Type type = new Type(); - type.setName(TypeName.valueOf(typeDoc.getString("name"))); - List weakAgainst = Optional - .ofNullable((List) typeDoc.get("weakAgainst")) - .orElse(Collections.emptyList()) - .stream() - .map(TypeName::valueOf) - .collect(Collectors.toList()); - type.setWeakAgainst(weakAgainst); - List effectiveAgainst = Optional - .ofNullable((List) typeDoc.get("effectiveAgainst")) - .orElse(Collections.emptyList()) - .stream() - .map(TypeName::valueOf) - .collect(Collectors.toList()); - type.setEffectiveAgainst(effectiveAgainst); - return type; - }) - .collect(Collectors.toList()); - pokemong.setTypes(types); - pokemong.setMoveSet(Optional.ofNullable(document.getList("moveSet", ObjectId.class)) - .orElse(Collections.emptyList())); - return pokemong; - } -} +package fr.uca.iut.codecs.pokemong; + +import com.mongodb.MongoClientSettings; +import fr.uca.iut.codecs.GenericCodec; +import fr.uca.iut.codecs.type.TypeCodecUtil; +import fr.uca.iut.entities.Pokemong; +import fr.uca.iut.entities.PokemongMove; +import fr.uca.iut.entities.Type; +import fr.uca.iut.utils.enums.PokemongName; +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.Document; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.types.ObjectId; + +import java.time.ZoneId; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class PokemongCodec extends GenericCodec { + private final Codec documentCodec; + + public PokemongCodec() { + this.documentCodec = MongoClientSettings.getDefaultCodecRegistry() + .get(Document.class); + } + + @Override + public void encode(BsonWriter writer, Pokemong pokemong, EncoderContext encoderContext) { + Document doc = new Document(); + + doc.put("_id", new ObjectId(pokemong.getId())); + + doc.put("nickname", pokemong.getNickname()); + + doc.put("dob", + Date.from(pokemong.getDob() + .atStartOfDay(ZoneId.systemDefault()) + .toInstant())); + + doc.put("level", pokemong.getLevel()); + + doc.put("pokedexId", pokemong.getPokedexId()); + + doc.put("evoStage", pokemong.getEvoStage()); + + List evoTrack = pokemong.getEvoTrack() + .stream() + .map(Enum::name) + .collect(Collectors.toList()); + doc.put("evoTrack", evoTrack); + + if (pokemong.getTrainer() != null) { + doc.put("trainer", new ObjectId(pokemong.getTrainer())); + } + + List types = pokemong.getTypes() + .stream() + .map(type -> { + Document typeDoc = new Document(); + typeDoc.put("name", + type.getName() + .name()); + List weakAgainst = type.getWeakAgainst() + .stream() + .map(Enum::name) + .collect(Collectors.toList()); + typeDoc.put("weakAgainst", weakAgainst); + List effectiveAgainst = type.getEffectiveAgainst() + .stream() + .map(Enum::name) + .collect(Collectors.toList()); + typeDoc.put("effectiveAgainst", effectiveAgainst); + return typeDoc; + }) + .collect(Collectors.toList()); + doc.put("types", types); + + List moveSetDocs = pokemong.getMoveSet() + .stream() + .map(move -> { + Document moveDoc = new Document(); + moveDoc.put("_id", new ObjectId(move.getId())); + moveDoc.put("name", move.getName()); + return moveDoc; + }) + .collect(Collectors.toList()); + doc.put("moveSet", moveSetDocs); + + documentCodec.encode(writer, doc, encoderContext); + } + + @Override + public Class getEncoderClass() { + return Pokemong.class; + } + + @Override + public Pokemong decode(BsonReader reader, DecoderContext decoderContext) { + Document document = documentCodec.decode(reader, decoderContext); + Pokemong pokemong = new Pokemong(); + + pokemong.setId(document.getObjectId("_id") + .toString()); + + pokemong.setNickname(document.getString("nickname")); + + Date dob = document.getDate("dob"); + if (dob != null) { + pokemong.setDob(dob.toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDate()); + } + + pokemong.setLevel(document.getInteger("level")); + + pokemong.setPokedexId(document.getInteger("pokedexId")); + + pokemong.setEvoStage(document.getInteger("evoStage")); + + List evoTrack = document.getList("evoTrack", String.class) + .stream() + .map(PokemongName::valueOf) + .collect(Collectors.toList()); + pokemong.setEvoTrack(evoTrack); + + pokemong.setTrainer(document.getObjectId("trainer") + .toString()); + + List types = document.getList("types", Document.class) + .stream() + .map(TypeCodecUtil::extractType) + .collect(Collectors.toList()); + pokemong.setTypes(types); + + Set moveSet = document.getList("moveSet", Document.class) + .stream() + .map(pokemongMoveDoc -> { + PokemongMove move = new PokemongMove(); + move.setId(((ObjectId) pokemongMoveDoc.get("_id")).toString()); + move.setName(pokemongMoveDoc.getString("name")); + return move; + }) + .collect(Collectors.toSet()); + pokemong.setMoveSet(moveSet); + + return pokemong; + } +} diff --git a/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodecProvider.java b/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodecProvider.java index 376412f..7327c19 100644 --- a/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodecProvider.java +++ b/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodecProvider.java @@ -1,16 +1,18 @@ -package fr.uca.iut.codecs.pokemong; - -import fr.uca.iut.entities.Pokemong; -import org.bson.codecs.Codec; -import org.bson.codecs.configuration.CodecProvider; -import org.bson.codecs.configuration.CodecRegistry; - -public class PokemongCodecProvider implements CodecProvider { - @Override - public Codec get(Class clazz, CodecRegistry registry) { - if (clazz.equals(Pokemong.class)) { - return (Codec) new PokemongCodec(); - } - return null; - } +package fr.uca.iut.codecs.pokemong; + +import com.mongodb.lang.Nullable; +import fr.uca.iut.entities.Pokemong; +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecProvider; +import org.bson.codecs.configuration.CodecRegistry; + +public class PokemongCodecProvider implements CodecProvider { + @Nullable + @Override + public Codec get(Class clazz, CodecRegistry registry) { + if (clazz.equals(Pokemong.class)) { + return (Codec) new PokemongCodec(); + } + return null; + } } \ No newline at end of file diff --git a/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java b/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java index ea2a396..f9c0041 100644 --- a/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java +++ b/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java @@ -1,57 +1,117 @@ -package fr.uca.iut.codecs.trainer; - -import com.mongodb.MongoClientSettings; -import fr.uca.iut.codecs.GenericCodec; -import fr.uca.iut.entities.Trainer; -import org.bson.*; -import org.bson.codecs.Codec; -import org.bson.codecs.DecoderContext; -import org.bson.codecs.EncoderContext; -import org.bson.types.ObjectId; - -import java.time.ZoneId; -import java.util.Date; - -public class TrainerCodec extends GenericCodec { - private final Codec documentCodec; - - public TrainerCodec() { - this.documentCodec = MongoClientSettings.getDefaultCodecRegistry() - .get(Document.class); - } - - @Override - public void encode(BsonWriter writer, Trainer trainer, EncoderContext encoderContext) { - Document doc = new Document(); - doc.put("_id", new ObjectId(trainer.getId())); - doc.put("name", trainer.getName()); - doc.put("dob", Date.from(trainer.getDob().atStartOfDay(ZoneId.systemDefault()).toInstant())); - doc.put("wins", trainer.getWins()); - doc.put("losses", trainer.getLosses()); - doc.put("pastOpponents", trainer.getPastOpponents()); - doc.put("pokemongs", trainer.getPokemongs()); - documentCodec.encode(writer, doc, encoderContext); - } - - @Override - public Class getEncoderClass() { - return Trainer.class; - } - - @Override - public Trainer decode(BsonReader reader, DecoderContext decoderContext) { - Document document = documentCodec.decode(reader, decoderContext); - Trainer trainer = new Trainer(); - trainer.setId(document.getObjectId("_id").toString()); - trainer.setName(document.getString("name")); - Date dob = document.getDate("dob"); - if (dob != null) { - trainer.setDob(dob.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()); - } - trainer.setWins(document.getInteger("wins", 0)); - trainer.setLosses(document.getInteger("losses", 0)); - trainer.setPastOpponents(document.getList("pastOpponents", ObjectId.class)); - trainer.setPokemongs(document.getList("pokemongs", ObjectId.class)); - return trainer; - } -} +package fr.uca.iut.codecs.trainer; + +import com.mongodb.MongoClientSettings; +import fr.uca.iut.codecs.GenericCodec; +import fr.uca.iut.entities.Trainer; +import fr.uca.iut.entities.TrainerPokemong; +import fr.uca.iut.utils.enums.PokemongName; +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.Document; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.types.ObjectId; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +public class TrainerCodec extends GenericCodec { + private final Codec documentCodec; + + public TrainerCodec() { + this.documentCodec = MongoClientSettings.getDefaultCodecRegistry() + .get(Document.class); + } + + @Override + public void encode(BsonWriter writer, Trainer trainer, EncoderContext encoderContext) { + Document doc = new Document(); + + doc.put("_id", new ObjectId(trainer.getId())); + + doc.put("name", trainer.getName()); + + LocalDate dob = trainer.getDob(); + if (dob != null) { + doc.put("dob", Date.from(dob.atStartOfDay(ZoneId.systemDefault()) + .toInstant())); + } + + doc.put("wins", trainer.getWins()); + + doc.put("losses", trainer.getLosses()); + + List pastOpponentsIds = trainer.getPastOpponents() + .stream() + .map(ObjectId::new) + .collect(Collectors.toList()); + doc.put("pastOpponents", pastOpponentsIds); + + List pokemongListDoc = trainer.getPokemongs() + .stream() + .map(pokemong -> { + Document moveDoc = new Document(); + moveDoc.put("_id", new ObjectId(pokemong.getId())); + moveDoc.put("nickname", pokemong.getNickname()); + moveDoc.put("species", + pokemong.getSpecies() + .name()); + return moveDoc; + }) + .collect(Collectors.toList()); + doc.put("pokemongs", pokemongListDoc); + + documentCodec.encode(writer, doc, encoderContext); + } + + @Override + public Class getEncoderClass() { + return Trainer.class; + } + + @Override + public Trainer decode(BsonReader reader, DecoderContext decoderContext) { + Document document = documentCodec.decode(reader, decoderContext); + Trainer trainer = new Trainer(); + + trainer.setId(document.getObjectId("_id") + .toString()); + + trainer.setName(document.getString("name")); + + Date dob = document.getDate("dob"); + if (dob != null) { + trainer.setDob(dob.toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDate()); + } + + trainer.setWins(document.getInteger("wins")); + + trainer.setLosses(document.getInteger("losses")); + + List pastOpponentsIds = document.getList("pastOpponents", ObjectId.class) + .stream() + .map(ObjectId::toString) + .collect(Collectors.toList()); + trainer.setPastOpponents(pastOpponentsIds); + + List pokemongList = document + .getList("pokemongs", Document.class) + .stream() + .map(pokemongDoc -> { + TrainerPokemong pokemong = new TrainerPokemong(); + pokemong.setId(((ObjectId) pokemongDoc.get("_id")).toString()); + pokemong.setNickname(pokemongDoc.getString("nickname")); + pokemong.setSpecies(PokemongName.valueOf(pokemongDoc.getString("species"))); + return pokemong; + }) + .collect(Collectors.toList()); + trainer.setPokemongs(pokemongList); + return trainer; + } +} diff --git a/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodecProvider.java b/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodecProvider.java index 80c178e..0c3e37a 100644 --- a/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodecProvider.java +++ b/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodecProvider.java @@ -1,16 +1,18 @@ -package fr.uca.iut.codecs.trainer; - -import fr.uca.iut.entities.Trainer; -import org.bson.codecs.Codec; -import org.bson.codecs.configuration.CodecProvider; -import org.bson.codecs.configuration.CodecRegistry; - -public class TrainerCodecProvider implements CodecProvider { - @Override - public Codec get(Class clazz, CodecRegistry registry) { - if (clazz.equals(Trainer.class)) { - return (Codec) new TrainerCodec(); - } - return null; - } -} +package fr.uca.iut.codecs.trainer; + +import com.mongodb.lang.Nullable; +import fr.uca.iut.entities.Trainer; +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecProvider; +import org.bson.codecs.configuration.CodecRegistry; + +public class TrainerCodecProvider implements CodecProvider { + @Nullable + @Override + public Codec get(Class clazz, CodecRegistry registry) { + if (clazz.equals(Trainer.class)) { + return (Codec) new TrainerCodec(); + } + return null; + } +} diff --git a/src/main/java/fr/uca/iut/codecs/type/TypeCodec.java b/src/main/java/fr/uca/iut/codecs/type/TypeCodec.java deleted file mode 100644 index 8b01381..0000000 --- a/src/main/java/fr/uca/iut/codecs/type/TypeCodec.java +++ /dev/null @@ -1,67 +0,0 @@ -package fr.uca.iut.codecs.type; - -import com.mongodb.MongoClientSettings; -import fr.uca.iut.entities.Type; -import fr.uca.iut.utils.TypeName; -import org.bson.*; -import org.bson.codecs.Codec; -import org.bson.codecs.DecoderContext; -import org.bson.codecs.EncoderContext; - -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -public class TypeCodec implements Codec { - private final Codec documentCodec; - - public TypeCodec() { - this.documentCodec = MongoClientSettings.getDefaultCodecRegistry() - .get(Document.class); - } - - @Override - public void encode(BsonWriter writer, Type type, EncoderContext encoderContext) { - Document doc = new Document(); - Optional.ofNullable(type.getName()) - .map(Enum::name) - .ifPresent(name -> doc.put("name", name)); - - Optional.ofNullable(type.getWeakAgainst()) - .map(weakAgainst -> weakAgainst.stream().map(Enum::name).collect(Collectors.toList())) - .ifPresent(weakAgainst -> doc.put("weakAgainst", weakAgainst)); - - Optional.ofNullable(type.getEffectiveAgainst()) - .map(effectiveAgainst -> effectiveAgainst.stream().map(Enum::name).collect(Collectors.toList())) - .ifPresent(effectiveAgainst -> doc.put("effectiveAgainst", effectiveAgainst)); - - documentCodec.encode(writer, doc, encoderContext); - } - - @Override - public Class getEncoderClass() { - return Type.class; - } - - @Override - public Type decode(BsonReader reader, DecoderContext decoderContext) { - Document document = documentCodec.decode(reader, decoderContext); - Type type = new Type(); - - Optional.ofNullable(document.getString("name")) - .map(TypeName::valueOf) - .ifPresent(type::setName); - - Optional.ofNullable(document.get("weakAgainst")) - .filter(obj -> obj instanceof List) - .map(obj -> ((List) obj).stream().map(TypeName::valueOf).collect(Collectors.toList())) - .ifPresent(type::setWeakAgainst); - - Optional.ofNullable(document.get("effectiveAgainst")) - .filter(obj -> obj instanceof List) - .map(obj -> ((List) obj).stream().map(TypeName::valueOf).collect(Collectors.toList())) - .ifPresent(type::setEffectiveAgainst); - - return type; - } -} diff --git a/src/main/java/fr/uca/iut/codecs/type/TypeCodecProvider.java b/src/main/java/fr/uca/iut/codecs/type/TypeCodecProvider.java deleted file mode 100644 index 4a75c95..0000000 --- a/src/main/java/fr/uca/iut/codecs/type/TypeCodecProvider.java +++ /dev/null @@ -1,16 +0,0 @@ -package fr.uca.iut.codecs.type; - -import fr.uca.iut.entities.Type; -import org.bson.codecs.Codec; -import org.bson.codecs.configuration.CodecProvider; -import org.bson.codecs.configuration.CodecRegistry; - -public class TypeCodecProvider implements CodecProvider { - @Override - public Codec get(Class clazz, CodecRegistry registry) { - if (clazz.equals(Type.class)) { - return (Codec) new TypeCodec(); - } - return null; - } -} diff --git a/src/main/java/fr/uca/iut/codecs/type/TypeCodecUtil.java b/src/main/java/fr/uca/iut/codecs/type/TypeCodecUtil.java new file mode 100644 index 0000000..bd45965 --- /dev/null +++ b/src/main/java/fr/uca/iut/codecs/type/TypeCodecUtil.java @@ -0,0 +1,27 @@ +package fr.uca.iut.codecs.type; + +import fr.uca.iut.entities.Type; +import fr.uca.iut.utils.enums.TypeName; +import org.bson.Document; + +import java.util.List; +import java.util.stream.Collectors; + +public class TypeCodecUtil { + public static Type extractType(Document typeDoc) { + Type type = new Type(); + type.setName(TypeName.valueOf(typeDoc.getString("name"))); + List weakAgainst = typeDoc.getList("weakAgainst", String.class) + .stream() + .map(TypeName::valueOf) + .collect(Collectors.toList()); + type.setWeakAgainst(weakAgainst); + List effectiveAgainst = typeDoc.getList("effectiveAgainst", + String.class) + .stream() + .map(TypeName::valueOf) + .collect(Collectors.toList()); + type.setEffectiveAgainst(effectiveAgainst); + return type; + } +} diff --git a/src/main/java/fr/uca/iut/controllers/GenericController.java b/src/main/java/fr/uca/iut/controllers/GenericController.java new file mode 100644 index 0000000..70b30d2 --- /dev/null +++ b/src/main/java/fr/uca/iut/controllers/GenericController.java @@ -0,0 +1,108 @@ +package fr.uca.iut.controllers; + +import fr.uca.iut.entities.GenericEntity; +import fr.uca.iut.services.GenericService; +import fr.uca.iut.utils.exceptions.NonValidEntityException; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +public abstract class GenericController { + + protected GenericService service; + + public void setService(GenericService service) { + this.service = service; + } + + @GET + @Path("/{id}") + public Response getOneById(@PathParam("id") String id) { + try { + T entity = service.getOneById(id); + if (entity != null) { + return Response.ok(entity) + .build(); + } + else { + return Response.status(Response.Status.NOT_FOUND) + .entity("Entity not found for id: " + id) + .build(); + } + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Invalid id format: " + id) + .build(); + } + } + + @GET + public Response getAll() { + return Response.ok(service.getAll()) + .build(); + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + public Response createOne(T entity) { + + try { + service.validateOne(entity); + T newEntity = service.addOne(entity); + + return Response.status(Response.Status.CREATED) + .entity(newEntity) + .build(); + + } catch (NonValidEntityException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(e.getMessage()) + .build(); + } + } + + @PUT + @Path("/{id}") + @Consumes(MediaType.APPLICATION_JSON) + public Response updateOne(@PathParam("id") String id, T entity) { + try { + service.validateOne(entity); + entity.setId(id); + T updatedEntity = service.updateOne(entity); + + if (updatedEntity != null) { + return Response.status(Response.Status.OK) + .entity(updatedEntity) + .build(); + } + else { + return Response.status(Response.Status.NOT_FOUND) + .entity("Entity not found for id: " + id) + .build(); + } + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Invalid id format: " + id) + .build(); + } catch (NonValidEntityException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(e.getMessage()) + .build(); + } + } + + @DELETE + @Path("/{id}") + public Response deleteOneById(@PathParam("id") String id) { + try { + service.deleteOneById(id); + return Response.ok() + .build(); + + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Invalid id format: " + id) + .build(); + } + } +} diff --git a/src/main/java/fr/uca/iut/controllers/MoveController.java b/src/main/java/fr/uca/iut/controllers/MoveController.java new file mode 100644 index 0000000..ff4dbaf --- /dev/null +++ b/src/main/java/fr/uca/iut/controllers/MoveController.java @@ -0,0 +1,22 @@ +package fr.uca.iut.controllers; + +import fr.uca.iut.entities.Move; +import fr.uca.iut.services.MoveService; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/move") +@Produces(MediaType.APPLICATION_JSON) +public class MoveController extends GenericController { + + @Inject + MoveService moveService; + + @PostConstruct + public void init() { + setService(moveService); + } +} diff --git a/src/main/java/fr/uca/iut/controllers/PokemongController.java b/src/main/java/fr/uca/iut/controllers/PokemongController.java index 9032f87..43f4000 100644 --- a/src/main/java/fr/uca/iut/controllers/PokemongController.java +++ b/src/main/java/fr/uca/iut/controllers/PokemongController.java @@ -1,102 +1,22 @@ -package fr.uca.iut.controllers; - -import fr.uca.iut.entities.Pokemong; -import fr.uca.iut.services.PokemongService; -import jakarta.inject.Inject; -import jakarta.ws.rs.*; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; - -@Path("/pokemong") -@Produces(MediaType.APPLICATION_JSON) -public class PokemongController { - - @Inject - PokemongService pokemongService; - - @GET - @Path("/{id}") - public Response getPokemong(@PathParam("id") String id) { - try { - Pokemong pokemong = pokemongService.getPokemong(id); - if (pokemong != null) { - return Response.ok(pokemong) - .build(); - } - else { - return Response.status(Response.Status.NOT_FOUND) - .entity("Pokemong not found for id: " + id) - .build(); - } - } catch (IllegalArgumentException e) { - return Response.status(Response.Status.BAD_REQUEST) - .entity("Invalid id format: " + id) - .build(); - } - } - - @GET - public Response getAllPokemongs() { - return Response.ok(pokemongService.getAllPokemongs()) - .build(); - } - - @POST - @Consumes(MediaType.APPLICATION_JSON) - public Response createPokemong(Pokemong pokemong) { - - if (pokemongService.isNotMature(pokemong)) { - pokemong.setMegaEvolved(null); - } - - Pokemong newPokemong = pokemongService.addPokemong(pokemong); - - return Response.status(Response.Status.CREATED) - .entity(newPokemong) - .build(); - } - - @PUT - @Path("/{id}") - @Consumes(MediaType.APPLICATION_JSON) - public Response updatePokemong(@PathParam("id") String id, Pokemong pokemong) { - try { - if (pokemongService.isNotMature(pokemong)) { - pokemong.setMegaEvolved(null); - } - - pokemong.setId(id); - Pokemong updatedPokemong = pokemongService.updatePokemong(pokemong); - - if (updatedPokemong != null) { - return Response.status(Response.Status.OK) - .entity(updatedPokemong) - .build(); - } - else { - return Response.status(Response.Status.NOT_FOUND) - .entity("Pokemong not found for id: " + id) - .build(); - } - } catch (IllegalArgumentException e) { - return Response.status(Response.Status.BAD_REQUEST) - .entity("Invalid id format: " + id) - .build(); - } - } - - @DELETE - @Path("/{id}") - public Response deletePokemong(@PathParam("id") String id) { - try { - pokemongService.deletePokemong(id); - return Response.ok() - .build(); - - } catch (IllegalArgumentException e) { - return Response.status(Response.Status.BAD_REQUEST) - .entity("Invalid id format: " + id) - .build(); - } - } -} +package fr.uca.iut.controllers; + +import fr.uca.iut.entities.Pokemong; +import fr.uca.iut.services.PokemongService; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/pokemong") +@Produces(MediaType.APPLICATION_JSON) +public class PokemongController extends GenericController { + + @Inject + PokemongService pokemongService; + + @PostConstruct + public void init() { + setService(pokemongService); + } +} diff --git a/src/main/java/fr/uca/iut/controllers/TrainerController.java b/src/main/java/fr/uca/iut/controllers/TrainerController.java new file mode 100644 index 0000000..da58bb3 --- /dev/null +++ b/src/main/java/fr/uca/iut/controllers/TrainerController.java @@ -0,0 +1,22 @@ +package fr.uca.iut.controllers; + +import fr.uca.iut.entities.Trainer; +import fr.uca.iut.services.TrainerService; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/trainer") +@Produces(MediaType.APPLICATION_JSON) +public class TrainerController extends GenericController { + + @Inject + TrainerService trainerService; + + @PostConstruct + public void init() { + setService(trainerService); + } +} diff --git a/src/main/java/fr/uca/iut/entities/GenericEntity.java b/src/main/java/fr/uca/iut/entities/GenericEntity.java index bcfbf04..f98460a 100644 --- a/src/main/java/fr/uca/iut/entities/GenericEntity.java +++ b/src/main/java/fr/uca/iut/entities/GenericEntity.java @@ -1,32 +1,32 @@ -package fr.uca.iut.entities; - -import org.bson.codecs.pojo.annotations.BsonId; - -import java.util.Objects; - -public abstract class GenericEntity { - - @BsonId - private String id; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - @Override - public int hashCode() { - return Objects.hash(id); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - GenericEntity entity = (GenericEntity) o; - return Objects.equals(id, entity.id); - } -} +package fr.uca.iut.entities; + +import org.bson.codecs.pojo.annotations.BsonId; + +import java.util.Objects; + +public abstract class GenericEntity { + + @BsonId + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GenericEntity entity = (GenericEntity) o; + return Objects.equals(id, entity.id); + } +} diff --git a/src/main/java/fr/uca/iut/entities/Move.java b/src/main/java/fr/uca/iut/entities/Move.java index d0dacc0..da98c37 100644 --- a/src/main/java/fr/uca/iut/entities/Move.java +++ b/src/main/java/fr/uca/iut/entities/Move.java @@ -1,65 +1,55 @@ -package fr.uca.iut.entities; - -import org.bson.codecs.pojo.annotations.BsonId; - -public class Move extends GenericEntity { - public static final String COLLECTION_NAME = "moves"; - - @BsonId - private String id; - private String name; - private String category; - private Integer power; - private Integer accuracy; - private Type type; - - public Move() {} - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getCategory() { - return category; - } - - public void setCategory(String category) { - this.category = category; - } - - public Integer getPower() { - return power; - } - - public void setPower(Integer power) { - this.power = power; - } - - public Integer getAccuracy() { - return accuracy; - } - - public void setAccuracy(Integer accuracy) { - this.accuracy = accuracy; - } - - public Type getType() { - return type; - } - - public void setType(Type type) { - this.type = type; - } +package fr.uca.iut.entities; + +import fr.uca.iut.utils.enums.MoveCategoryName; + +public class Move extends GenericEntity { + public static final String COLLECTION_NAME = "moves"; + + private String name; + private MoveCategoryName category; + private Integer power; + private Integer accuracy; + private Type type; + + public Move() {} + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public MoveCategoryName getCategory() { + return category; + } + + public void setCategory(MoveCategoryName category) { + this.category = category; + } + + public Integer getPower() { + return power; + } + + public void setPower(Integer power) { + this.power = power; + } + + public Integer getAccuracy() { + return accuracy; + } + + public void setAccuracy(Integer accuracy) { + this.accuracy = accuracy; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } } \ No newline at end of file diff --git a/src/main/java/fr/uca/iut/entities/Pokemong.java b/src/main/java/fr/uca/iut/entities/Pokemong.java index d55deee..1acd9e4 100644 --- a/src/main/java/fr/uca/iut/entities/Pokemong.java +++ b/src/main/java/fr/uca/iut/entities/Pokemong.java @@ -1,119 +1,127 @@ -package fr.uca.iut.entities; - -import fr.uca.iut.utils.PokemongName; -import org.bson.codecs.pojo.annotations.BsonId; -import org.bson.types.ObjectId; - -import java.time.LocalDate; -import java.util.List; - -public class Pokemong extends GenericEntity { - public static final String COLLECTION_NAME = "pokemongs"; - @BsonId - private String id; - private String nickname; - private LocalDate dob; - private Integer level; - private Integer pokedexId; - private Integer evoStage; - private List evoTrack; - private Boolean isMegaEvolved; - private ObjectId trainer; - private List types; // TODO Bound this within [1;2] (in controller) - private List moveSet; // TODO Bound this within [1;4] (in controller) and denormalize move "name" - - public Pokemong() {} - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getNickname() { - return nickname; - } - - public void setNickname(String nickname) { - this.nickname = nickname; - } - - public LocalDate getDob() { - return dob; - } - - public void setDob(LocalDate dob) { - this.dob = dob; - } - - public Integer getLevel() { - return level; - } - - public void setLevel(Integer level) { - this.level = level; - } - - public Integer getPokedexId() { - return pokedexId; - } - - public void setPokedexId(Integer pokedexId) { - this.pokedexId = pokedexId; - } - - public Integer getEvoStage() { - return evoStage; - } - - public void setEvoStage(Integer evoStage) { - this.evoStage = evoStage; - } - - public List getEvoTrack() { - return evoTrack; - } - - public void setEvoTrack(List evoTrack) { - this.evoTrack = evoTrack; - } - - public Boolean getMegaEvolved() { - return isMegaEvolved; - } - - public void setMegaEvolved(Boolean megaEvolved) { - isMegaEvolved = megaEvolved; - } - - public ObjectId getTrainer() { - return trainer; - } - - public void setTrainer(ObjectId trainer) { - this.trainer = trainer; - } - - // TODO take particular care with collections - - // TODO study the question of encapsulation when it comes to using these dependencies... - public List getTypes() { - return types; - } - - public void setTypes(List types) { - this.types = types; - } - - public List getMoveSet() { - return moveSet; - } - - public void setMoveSet(List moveSet) { - this.moveSet = moveSet; - } - -} - +package fr.uca.iut.entities; + +import com.mongodb.lang.Nullable; +import fr.uca.iut.utils.enums.PokemongName; + +import java.time.LocalDate; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class Pokemong extends GenericEntity { + public static final String COLLECTION_NAME = "pokemongs"; + + @Nullable + private String nickname; + private LocalDate dob; + private Integer level; + private Integer pokedexId; + private Integer evoStage; + private List evoTrack; + @Nullable + private String trainer; + private List types; + + /** + * pokemong.moveSet: [{_id: ObjectId, name: String}] + */ + private Set moveSet; + + public Pokemong() {} + + @Nullable + public String getNickname() { + return nickname; + } + + public void setNickname(@Nullable String nickname) { + this.nickname = nickname; + } + + public LocalDate getDob() { + return dob; + } + + public void setDob(LocalDate dob) { + this.dob = dob; + } + + public Integer getLevel() { + return level; + } + + public void setLevel(Integer level) { + this.level = level; + } + + public Integer getPokedexId() { + return pokedexId; + } + + public void setPokedexId(Integer pokedexId) { + this.pokedexId = pokedexId; + } + + @Nullable + public String getTrainer() { + return trainer; + } + + public void setTrainer(@Nullable String trainer) { + this.trainer = trainer; + } + + public List getTypes() { + return Collections.unmodifiableList(types); + } + + public void setTypes(List types) { + this.types = types; + } + + public Set getMoveSet() { + return Collections.unmodifiableSet(moveSet); + } + + public void setMoveSet(Set moveSet) { + this.moveSet = moveSet; + } + + public void removeMove(String id) { + PokemongMove pokemongMove = new PokemongMove(); + pokemongMove.setId(id); + moveSet.remove(pokemongMove); + } + + public void updateMove(String id, String name) { + for (PokemongMove move : moveSet) { + if (move.getId() + .equals(id)) + { + move.setName(name); + break; + } + } + } + + public PokemongName getSpecies() { + return getEvoTrack().get(getEvoStage()); + } + + public List getEvoTrack() { + return evoTrack; + } + + public Integer getEvoStage() { + return evoStage; + } + + public void setEvoStage(Integer evoStage) { + this.evoStage = evoStage; + } + + public void setEvoTrack(List evoTrack) { + this.evoTrack = evoTrack; + } +} + diff --git a/src/main/java/fr/uca/iut/entities/PokemongMove.java b/src/main/java/fr/uca/iut/entities/PokemongMove.java new file mode 100644 index 0000000..13c4c31 --- /dev/null +++ b/src/main/java/fr/uca/iut/entities/PokemongMove.java @@ -0,0 +1,16 @@ +package fr.uca.iut.entities; + +public class PokemongMove extends GenericEntity { + + private String name; + + public PokemongMove() {} + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/fr/uca/iut/entities/Trainer.java b/src/main/java/fr/uca/iut/entities/Trainer.java index 7b920e3..6586349 100644 --- a/src/main/java/fr/uca/iut/entities/Trainer.java +++ b/src/main/java/fr/uca/iut/entities/Trainer.java @@ -1,78 +1,66 @@ -package fr.uca.iut.entities; - -import org.bson.codecs.pojo.annotations.BsonId; -import org.bson.types.ObjectId; - -import java.time.LocalDate; -import java.util.List; - -public class Trainer extends GenericEntity { - public static final String COLLECTION_NAME = "trainers"; - - @BsonId - private String id; - private String name; - private LocalDate dob; - private Integer wins; - private Integer losses; - private List pastOpponents; - private List pokemongs; - - public Trainer() {} - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public LocalDate getDob() { - return dob; - } - - public void setDob(LocalDate dob) { - this.dob = dob; - } - - public Integer getWins() { - return wins; - } - - public void setWins(Integer wins) { - this.wins = wins; - } - - public Integer getLosses() { - return losses; - } - - public void setLosses(Integer losses) { - this.losses = losses; - } - - public List getPastOpponents() { - return pastOpponents; - } - - public void setPastOpponents(List pastOpponents) { - this.pastOpponents = pastOpponents; - } - - public List getPokemongs() { - return pokemongs; - } - - public void setPokemongs(List pokemongs) { - this.pokemongs = pokemongs; - } -} +package fr.uca.iut.entities; + +import java.time.LocalDate; +import java.util.Collections; +import java.util.List; + +public class Trainer extends GenericEntity { + public static final String COLLECTION_NAME = "trainers"; + + private String name; + private LocalDate dob; + private Integer wins; + private Integer losses; + private List pastOpponents; + private List pokemongs; + + public Trainer() {} + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public LocalDate getDob() { + return dob; + } + + public void setDob(LocalDate dob) { + this.dob = dob; + } + + public Integer getWins() { + return wins; + } + + public void setWins(Integer wins) { + this.wins = wins; + } + + public Integer getLosses() { + return losses; + } + + public void setLosses(Integer losses) { + this.losses = losses; + } + + public List getPastOpponents() { + return Collections.unmodifiableList(pastOpponents); + } + + public void setPastOpponents(List pastOpponents) { + this.pastOpponents = pastOpponents; + } + + public List getPokemongs() { + return Collections.unmodifiableList(pokemongs); + } + + public void setPokemongs(List pokemongs) { + this.pokemongs = pokemongs; + } +} diff --git a/src/main/java/fr/uca/iut/entities/TrainerPokemong.java b/src/main/java/fr/uca/iut/entities/TrainerPokemong.java new file mode 100644 index 0000000..f63fcb5 --- /dev/null +++ b/src/main/java/fr/uca/iut/entities/TrainerPokemong.java @@ -0,0 +1,30 @@ +package fr.uca.iut.entities; + +import com.mongodb.lang.Nullable; +import fr.uca.iut.utils.enums.PokemongName; + +public class TrainerPokemong extends GenericEntity { + @Nullable + private String nickname; + + private PokemongName species; + + public TrainerPokemong() {} + + @Nullable + public String getNickname() { + return nickname; + } + + public void setNickname(@Nullable String nickname) { + this.nickname = nickname; + } + + public PokemongName getSpecies() { + return species; + } + + public void setSpecies(PokemongName species) { + this.species = species; + } +} diff --git a/src/main/java/fr/uca/iut/entities/Type.java b/src/main/java/fr/uca/iut/entities/Type.java index 6eb5756..f9e3022 100644 --- a/src/main/java/fr/uca/iut/entities/Type.java +++ b/src/main/java/fr/uca/iut/entities/Type.java @@ -1,55 +1,56 @@ -package fr.uca.iut.entities; - -import fr.uca.iut.utils.TypeName; - -import java.util.List; -import java.util.Objects; - -public class Type { - - private TypeName name; - private List weakAgainst; - private List effectiveAgainst; - - public Type() {} - - public TypeName getName() { - return name; - } - - public void setName(TypeName name) { - this.name = name; - } - - public List getWeakAgainst() { - return weakAgainst; - } - - public void setWeakAgainst(List weakAgainst) { - this.weakAgainst = weakAgainst; - } - - public List getEffectiveAgainst() { - return effectiveAgainst; - } - - public void setEffectiveAgainst(List effectiveAgainst) { - this.effectiveAgainst = effectiveAgainst; - } - - @Override - public int hashCode() { - return Objects.hash(name, weakAgainst, effectiveAgainst); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Type type = (Type) o; - return Objects.equals(name, type.name) && - Objects.equals(weakAgainst, type.weakAgainst) && - Objects.equals(effectiveAgainst, type.effectiveAgainst); - } - +package fr.uca.iut.entities; + +import fr.uca.iut.utils.enums.TypeName; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class Type { + + private TypeName name; + private List weakAgainst; + private List effectiveAgainst; + + public Type() {} + + public TypeName getName() { + return name; + } + + public void setName(TypeName name) { + this.name = name; + } + + public List getWeakAgainst() { + return Collections.unmodifiableList(weakAgainst); + } + + public void setWeakAgainst(List weakAgainst) { + this.weakAgainst = weakAgainst; + } + + public List getEffectiveAgainst() { + return Collections.unmodifiableList(effectiveAgainst); + } + + public void setEffectiveAgainst(List effectiveAgainst) { + this.effectiveAgainst = effectiveAgainst; + } + + @Override + public int hashCode() { + return Objects.hash(name, weakAgainst, effectiveAgainst); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Type type = (Type) o; + return Objects.equals(name, type.name) && + Objects.equals(weakAgainst, type.weakAgainst) && + Objects.equals(effectiveAgainst, type.effectiveAgainst); + } + } \ No newline at end of file diff --git a/src/main/java/fr/uca/iut/package-info.java b/src/main/java/fr/uca/iut/package-info.java new file mode 100644 index 0000000..3d301f9 --- /dev/null +++ b/src/main/java/fr/uca/iut/package-info.java @@ -0,0 +1,4 @@ +@NonNullApi +package fr.uca.iut; + +import com.mongodb.lang.NonNullApi; diff --git a/src/main/java/fr/uca/iut/repositories/GenericRepository.java b/src/main/java/fr/uca/iut/repositories/GenericRepository.java new file mode 100644 index 0000000..649e2a4 --- /dev/null +++ b/src/main/java/fr/uca/iut/repositories/GenericRepository.java @@ -0,0 +1,60 @@ +package fr.uca.iut.repositories; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.ReplaceOptions; +import com.mongodb.lang.Nullable; +import fr.uca.iut.entities.GenericEntity; +import org.bson.Document; +import org.bson.types.ObjectId; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +import static com.mongodb.client.model.Filters.eq; + +public abstract class GenericRepository { + protected MongoClient mongoClient; + @ConfigProperty(name = "quarkus.mongodb.database") + String DB_NAME; + + public void setMongoClient(MongoClient mongoClient) { + this.mongoClient = mongoClient; + } + + @Nullable + public T findById(String id) { + return getCollection().find(eq("_id", new ObjectId(id))) + .first(); + } + + protected abstract MongoCollection getCollection(); + + public void persist(@NotNull T entity) { + getCollection().insertOne(entity); + } + + public List listAll() { + return getCollection().find() + .into(new ArrayList<>()); + } + + public void persistOrUpdate(@NotNull T entity) { + getCollection().replaceOne( + eq("_id", new ObjectId(entity.getId())), + entity, + new ReplaceOptions().upsert(true) + ); + } + + public void delete(@NotNull T entity) { + getCollection().deleteOne(eq("_id", new ObjectId(entity.getId()))); + } + + public boolean existsById(String id) { + Document query = new Document("_id", new ObjectId(id)); + return getCollection().countDocuments(query) > 0; + } +} diff --git a/src/main/java/fr/uca/iut/repositories/MoveRepository.java b/src/main/java/fr/uca/iut/repositories/MoveRepository.java new file mode 100644 index 0000000..0226f04 --- /dev/null +++ b/src/main/java/fr/uca/iut/repositories/MoveRepository.java @@ -0,0 +1,32 @@ +package fr.uca.iut.repositories; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import fr.uca.iut.entities.Move; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +@ApplicationScoped +public class MoveRepository extends GenericRepository { + + // FIX?ME + /** + * Warns that "Unsatisfied dependency: no bean matches the injection point" + * but the app works + */ + @Inject + MongoClient mongoClient; + + @PostConstruct + public void init() { + setMongoClient(mongoClient); + } + + @Override + protected MongoCollection getCollection() { + MongoDatabase db = mongoClient.getDatabase(DB_NAME); + return db.getCollection(Move.COLLECTION_NAME, Move.class); + } +} diff --git a/src/main/java/fr/uca/iut/repositories/PokemongRepository.java b/src/main/java/fr/uca/iut/repositories/PokemongRepository.java index f60ec5d..3da2595 100644 --- a/src/main/java/fr/uca/iut/repositories/PokemongRepository.java +++ b/src/main/java/fr/uca/iut/repositories/PokemongRepository.java @@ -1,58 +1,45 @@ -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.ReplaceOptions; -import fr.uca.iut.entities.Pokemong; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import org.bson.types.ObjectId; -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import java.util.ArrayList; -import java.util.List; - -import static com.mongodb.client.model.Filters.eq; - -@ApplicationScoped -public class PokemongRepository { - - // FIXME? or suppress warning: "Unsatisfied dependency: no bean matches the injection point" - @Inject - MongoClient mongoClient; - - @ConfigProperty(name = "quarkus.mongodb.database") - String DB_NAME; - - private MongoCollection getCollection() { - MongoDatabase db = mongoClient.getDatabase(DB_NAME); - return db.getCollection(Pokemong.COLLECTION_NAME, Pokemong.class); - } - - public Pokemong findById(String id) { - return getCollection().find(eq("_id", new ObjectId(id))) - .first(); - } - - public void persist(Pokemong pokemong) { - getCollection().insertOne(pokemong); - } - - public List listAll() { - return getCollection().find() - .into(new ArrayList<>()); - } - - public void delete(Pokemong pokemong) { - getCollection().deleteOne(eq("_id", new ObjectId(pokemong.getId()))); - } - - public void persistOrUpdate(Pokemong pokemong) { - getCollection().replaceOne( - eq("_id", new ObjectId(pokemong.getId())), - pokemong, - new ReplaceOptions().upsert(true) - ); - } -} +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.Filters; +import fr.uca.iut.entities.Pokemong; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.bson.conversions.Bson; +import org.bson.types.ObjectId; + +import java.util.ArrayList; +import java.util.List; + +@ApplicationScoped +public class PokemongRepository extends GenericRepository { + + // FIX?ME + /** + * Warns that "Unsatisfied dependency: no bean matches the injection point" + * but the app works + */ + @Inject + MongoClient mongoClient; + + @PostConstruct + public void init() { + setMongoClient(mongoClient); + } + + public List findByMove(String moveId) { + Bson filter = Filters.elemMatch("moveSet", Filters.eq("_id", new ObjectId(moveId))); + return getCollection().find(filter) + .into(new ArrayList<>()); + } + + @Override + protected MongoCollection getCollection() { + MongoDatabase db = mongoClient.getDatabase(DB_NAME); + return db.getCollection(Pokemong.COLLECTION_NAME, Pokemong.class); + } + +} diff --git a/src/main/java/fr/uca/iut/repositories/TrainerRepository.java b/src/main/java/fr/uca/iut/repositories/TrainerRepository.java new file mode 100644 index 0000000..35ff7ed --- /dev/null +++ b/src/main/java/fr/uca/iut/repositories/TrainerRepository.java @@ -0,0 +1,32 @@ +package fr.uca.iut.repositories; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import fr.uca.iut.entities.Trainer; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +@ApplicationScoped +public class TrainerRepository extends GenericRepository { + + // FIX?ME + /** + * Warns that "Unsatisfied dependency: no bean matches the injection point" + * but the app works + */ + @Inject + MongoClient mongoClient; + + @PostConstruct + public void init() { + setMongoClient(mongoClient); + } + + @Override + protected MongoCollection getCollection() { + MongoDatabase db = mongoClient.getDatabase(DB_NAME); + return db.getCollection(Trainer.COLLECTION_NAME, Trainer.class); + } +} diff --git a/src/main/java/fr/uca/iut/services/GenericService.java b/src/main/java/fr/uca/iut/services/GenericService.java new file mode 100644 index 0000000..3bd1435 --- /dev/null +++ b/src/main/java/fr/uca/iut/services/GenericService.java @@ -0,0 +1,51 @@ +package fr.uca.iut.services; + +import com.mongodb.lang.Nullable; +import fr.uca.iut.entities.GenericEntity; +import fr.uca.iut.repositories.GenericRepository; +import fr.uca.iut.utils.exceptions.NonValidEntityException; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public abstract class GenericService { + + protected GenericRepository repository; + + public void setRepository(GenericRepository repository) { + this.repository = repository; + } + + public T addOne(@NotNull T entity) { + repository.persist(entity); + return entity; + } + + @Nullable + public T getOneById(String id) { + return repository.findById(id); + } + + public List getAll() { + return repository.listAll(); + } + + public void deleteOneById(String id) { + T entity = repository.findById(id); + if (entity != null) { + repository.delete(entity); + } + } + + @Nullable + public abstract T updateOne(@NotNull T entity); + + /** + * Override me and start with `super.validateOne(entity);` + */ + public void validateOne(T entity) { + if (entity == null) { + throw new NonValidEntityException("entity was null"); + } + } +} diff --git a/src/main/java/fr/uca/iut/services/MoveService.java b/src/main/java/fr/uca/iut/services/MoveService.java new file mode 100644 index 0000000..ed30c0e --- /dev/null +++ b/src/main/java/fr/uca/iut/services/MoveService.java @@ -0,0 +1,100 @@ +package fr.uca.iut.services; + +import com.mongodb.lang.Nullable; +import fr.uca.iut.entities.Move; +import fr.uca.iut.entities.Pokemong; +import fr.uca.iut.repositories.MoveRepository; +import fr.uca.iut.utils.StringUtils; +import fr.uca.iut.utils.exceptions.NonValidEntityException; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +@ApplicationScoped +public class MoveService extends GenericService { + + @Inject + MoveRepository moveRepository; + @Inject + PokemongService pokemongService; + + @PostConstruct + public void init() { + setRepository(moveRepository); + } + + @Override + public void deleteOneById(String id) { + super.deleteOneById(id); + List pokemongs = pokemongService.findByMove(id); + for (Pokemong pokemong : pokemongs) { + pokemong.removeMove(id); + pokemongService.updateOne(pokemong); + } + } + + @Override + @Nullable + public Move updateOne(@NotNull Move move) { + Move existingMove = moveRepository.findById(move.getId()); + if (existingMove != null) { + if (!existingMove.getName() + .equals(move.getName())) + { + existingMove.setName(move.getName()); + List pokemongs = pokemongService.findByMove(move.getId()); + for (Pokemong pokemong : pokemongs) { + pokemong.updateMove(move.getId(), move.getName()); + pokemongService.updateOne(pokemong); + } + } + + existingMove.setPower(move.getPower()); + existingMove.setCategory(move.getCategory()); + existingMove.setAccuracy(move.getAccuracy()); + existingMove.setType(move.getType()); + moveRepository.persistOrUpdate(existingMove); + } + return existingMove; + } + + @Override + public void validateOne(Move move) { + + super.validateOne(move); + + List errors = new ArrayList<>(); + + if (StringUtils.isBlankStringOrNull(move.getName())) { + errors.add("move name was null, blank or empty"); + } + + if (move.getPower() == null || move.getPower() < 0) { + errors.add("move power was null or negative"); + } + + if (move.getCategory() == null) { + errors.add("move category was null or invalid"); + } + + if (move.getAccuracy() == null || move.getAccuracy() < 0) { + errors.add("move accuracy was null or negative"); + } + + if (move.getType() == null) { + errors.add("move type was null or invalid"); + } + + if (!errors.isEmpty()) { + throw new NonValidEntityException("Validation errors: " + String.join(", ", errors)); + } + } + + public boolean existsById(String moveId) { + return moveRepository.existsById(moveId); + } +} diff --git a/src/main/java/fr/uca/iut/services/PokemongService.java b/src/main/java/fr/uca/iut/services/PokemongService.java index b7d47a5..ae9afd9 100644 --- a/src/main/java/fr/uca/iut/services/PokemongService.java +++ b/src/main/java/fr/uca/iut/services/PokemongService.java @@ -1,65 +1,202 @@ -package fr.uca.iut.services; - -import fr.uca.iut.entities.Pokemong; -import fr.uca.iut.repositories.PokemongRepository; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -import java.util.List; - -@ApplicationScoped -public class PokemongService { - - @Inject - PokemongRepository pokemongRepository; - - public Pokemong addPokemong(Pokemong pokemong) { - pokemongRepository.persist(pokemong); - return pokemong; - } - - public Pokemong getPokemong(String id) { - return pokemongRepository.findById(id); - } - - public List getAllPokemongs() { - return pokemongRepository.listAll(); - } - - public void deletePokemong(String id) { - Pokemong pokemong = pokemongRepository.findById(id); - if (pokemong != null) { - pokemongRepository.delete(pokemong); - } - } - - public Pokemong updatePokemong(Pokemong pokemong) { - Pokemong existingPokemong = pokemongRepository.findById(pokemong.getId()); - if (existingPokemong != null) { - existingPokemong.setNickname(pokemong.getNickname()); - existingPokemong.setDob(pokemong.getDob()); - existingPokemong.setLevel(pokemong.getLevel()); - existingPokemong.setPokedexId(pokemong.getPokedexId()); - existingPokemong.setEvoStage(pokemong.getEvoStage()); - existingPokemong.setEvoTrack(pokemong.getEvoTrack()); - existingPokemong.setMegaEvolved(pokemong.getMegaEvolved()); - existingPokemong.setTrainer(pokemong.getTrainer()); - existingPokemong.setTypes(pokemong.getTypes()); - existingPokemong.setMoveSet(pokemong.getMoveSet()); - pokemongRepository.persistOrUpdate(existingPokemong); - } - return existingPokemong; - } - - public boolean isNotMature(Pokemong pokemong) { - return pokemong == null - || pokemong.getEvoStage() == null - || pokemong.getEvoTrack() == null - || pokemong.getEvoTrack() - .isEmpty() - || (pokemong.getEvoStage() != pokemong.getEvoTrack() - .size() - 1); - } - - // TODO PATCH ? -} +package fr.uca.iut.services; + +import com.mongodb.lang.Nullable; +import fr.uca.iut.entities.*; +import fr.uca.iut.repositories.PokemongRepository; +import fr.uca.iut.utils.StringUtils; +import fr.uca.iut.utils.enums.PokemongName; +import fr.uca.iut.utils.exceptions.NonValidEntityException; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +@ApplicationScoped +public class PokemongService extends GenericService { + + @Inject + PokemongRepository pokemongRepository; + + @Inject + MoveService moveService; + + @Inject + TrainerService trainerService; + + @PostConstruct + public void init() { + setRepository(pokemongRepository); + } + + @Override + public Pokemong addOne(@NotNull Pokemong pokemong) { + Pokemong persistedPokemong = super.addOne(pokemong); + + Trainer trainer = trainerService.getOneById(pokemong.getTrainer()); + if (trainer != null) { + TrainerPokemong trainerPokemong = new TrainerPokemong(); + trainerPokemong.setId(pokemong.getId()); + trainerPokemong.setNickname(pokemong.getNickname()); + trainerPokemong.setSpecies(pokemong.getSpecies()); + trainer.getPokemongs() + .add(trainerPokemong); + trainerService.updateOne(trainer); + } + return persistedPokemong; + } + + @Override + public void deleteOneById(String id) { + Pokemong pokemong = getOneById(id); + if (pokemong != null && pokemong.getTrainer() != null) { + Trainer trainer = trainerService.getOneById(pokemong.getTrainer()); + if (trainer != null) { + trainer.getPokemongs() + .removeIf(trainerPokemong -> trainerPokemong.getId() + .equals(id)); + trainerService.updateOne(trainer); + } + } + super.deleteOneById(id); + } + + @Override + @Nullable + public Pokemong updateOne(@NotNull Pokemong pokemong) { + Pokemong existingPokemong = pokemongRepository.findById(pokemong.getId()); + if (existingPokemong != null) { + boolean nicknameChanged = !Objects.equals(existingPokemong.getNickname(), pokemong.getNickname()); + boolean evoStageChanged = !Objects.equals(existingPokemong.getEvoStage(), pokemong.getEvoStage()); + boolean evoTrackChanged = !Objects.equals(existingPokemong.getEvoTrack(), pokemong.getEvoTrack()); + + existingPokemong.setNickname(pokemong.getNickname()); + existingPokemong.setDob(pokemong.getDob()); + existingPokemong.setLevel(pokemong.getLevel()); + existingPokemong.setPokedexId(pokemong.getPokedexId()); + existingPokemong.setEvoStage(pokemong.getEvoStage()); + existingPokemong.setEvoTrack(pokemong.getEvoTrack()); + existingPokemong.setTrainer(pokemong.getTrainer()); + existingPokemong.setTypes(pokemong.getTypes()); + existingPokemong.setMoveSet(pokemong.getMoveSet()); + + pokemongRepository.persistOrUpdate(existingPokemong); + + if (nicknameChanged || evoStageChanged || evoTrackChanged) { + Trainer trainer = trainerService.getOneById(existingPokemong.getTrainer()); + if (trainer != null) { + TrainerPokemong trainerPokemong = trainer.getPokemongs() + .stream() + .filter(tp -> tp.getId() + .equals(existingPokemong.getId())) + .findFirst() + .orElse(null); + + if (trainerPokemong != null) { + if (nicknameChanged) { + trainerPokemong.setNickname(existingPokemong.getNickname()); + } + + if (evoStageChanged || evoTrackChanged) { + trainerPokemong.setSpecies(existingPokemong.getSpecies()); + } + + trainerService.updateOne(trainer); + } + } + } + } + return existingPokemong; + } + + @Override + public void validateOne(Pokemong pokemong) { + + super.validateOne(pokemong); + + List errors = new ArrayList<>(); + + if (pokemong.getDob() == null) { + errors.add("pokemong date of birth was null or invalid"); + } + + if (pokemong.getLevel() == null || pokemong.getLevel() < 1) { + errors.add("pokemong level was null or less than 1"); + } + + if (pokemong.getPokedexId() == null || pokemong.getPokedexId() < 1) { + errors.add("pokemong pokedex id was null or less than 1"); + } + + if (pokemong.getEvoStage() == null || pokemong.getEvoStage() < 0) { + errors.add("pokemong evo stage was null or negative"); + } + + if (pokemong.getEvoTrack() == null) { + errors.add("pokemong evo track was null or invalid"); + } + + List types = pokemong.getTypes(); + if (types == null + || types.size() == 0 + || types.size() > 2) + { + errors.add("pokemong types was null or empty or had more than 2 types"); + } + + Set moveSet = pokemong.getMoveSet(); + if (moveSet == null) { + errors.add("pokemong move set was null"); + } + else { + if (moveSet.size() == 0 || moveSet.size() > 4) { + errors.add("pokemong move set was empty or had more than 4 moves"); + } + for (PokemongMove move : moveSet) { + String moveId = move.getId(); + String moveName = move.getName(); + if (StringUtils.isBlankStringOrNull(moveId) || !moveService.existsById(moveId)) { + errors.add("move with id " + moveId + " does not exist"); + } + if (StringUtils.isBlankStringOrNull(moveName)) { + errors.add("move name was null, blank or empty"); + } + // We don't check whether the move name is consistent with the original -- trainers can rename moves + // locally in a pokemong. If once in a while a Move has its name updated, the change will be reflected + // in all the PokemongMoves, and the local aliases will be lost + } + } + + if (!errors.isEmpty()) { + throw new NonValidEntityException("Validation errors: " + String.join(", ", errors)); + } + } + + public List findByMove(String id) { + return pokemongRepository.findByMove(id); + } + + public boolean isEvoValid(String id, PokemongName species) { + Pokemong pokemong = pokemongRepository.findById(id); + + return pokemong != null && pokemong.getSpecies() == species; + } + + public boolean existsById(String pokemongId) { + return repository.existsById(pokemongId); + } + + public void batchUpdatePokemongTrainers(List trainerPokemongs, @Nullable String trainerId) { + for (TrainerPokemong trainerPokemong : trainerPokemongs) { + Pokemong pokemong = getOneById(trainerPokemong.getId()); + if (pokemong != null) { + pokemong.setTrainer(trainerId); + updateOne(pokemong); + } + } + } +} diff --git a/src/main/java/fr/uca/iut/services/TrainerService.java b/src/main/java/fr/uca/iut/services/TrainerService.java new file mode 100644 index 0000000..65d8a88 --- /dev/null +++ b/src/main/java/fr/uca/iut/services/TrainerService.java @@ -0,0 +1,136 @@ +package fr.uca.iut.services; + +import com.mongodb.lang.Nullable; +import fr.uca.iut.entities.Pokemong; +import fr.uca.iut.entities.Trainer; +import fr.uca.iut.entities.TrainerPokemong; +import fr.uca.iut.repositories.TrainerRepository; +import fr.uca.iut.utils.StringUtils; +import fr.uca.iut.utils.exceptions.NonValidEntityException; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +@ApplicationScoped +public class TrainerService extends GenericService { + + @Inject + TrainerRepository trainerRepository; + + @Inject + PokemongService pokemongService; + + @PostConstruct + public void init() { + setRepository(trainerRepository); + } + + @Override + public Trainer addOne(@NotNull Trainer trainer) { + Trainer persistedTrainer = super.addOne(trainer); + + pokemongService.batchUpdatePokemongTrainers(trainer.getPokemongs(), trainer.getId()); + + return persistedTrainer; + } + + @Override + public void deleteOneById(String id) { + Trainer trainer = getOneById(id); + + if (trainer != null) { + pokemongService.batchUpdatePokemongTrainers(trainer.getPokemongs(), null); + } + + super.deleteOneById(id); + } + + @Nullable + @Override + public Trainer updateOne(@NotNull Trainer trainer) { + Trainer existingTrainer = trainerRepository.findById(trainer.getId()); + if (existingTrainer != null) { + existingTrainer.setName(trainer.getName()); + existingTrainer.setDob(trainer.getDob()); + existingTrainer.setWins(trainer.getLosses()); + existingTrainer.setLosses(trainer.getLosses()); + existingTrainer.setPastOpponents(trainer.getPastOpponents()); + existingTrainer.setPokemongs(trainer.getPokemongs()); + trainerRepository.persistOrUpdate(existingTrainer); + } + return existingTrainer; + } + + @Override + public void validateOne(Trainer trainer) { + + super.validateOne(trainer); + + List errors = new ArrayList<>(); + + if (StringUtils.isBlankStringOrNull(trainer.getName())) { + errors.add("trainer name was null, blank or empty"); + } + + if (trainer.getDob() == null) { + errors.add("trainer date of birth was null or invalid"); + } + + if (trainer.getLosses() == null || trainer.getLosses() < 0) { + errors.add("trainer losses was null or negative"); + } + + if (trainer.getWins() == null || trainer.getWins() < 0) { + errors.add("trainer wins was null or negative"); + } + + List pastOpponents = trainer.getPastOpponents(); + + if (pastOpponents == null) { + errors.add("trainer past opponents collection was null"); + } + else { + for (String trainerId : pastOpponents) { + if (StringUtils.isBlankStringOrNull(trainerId) || !trainerRepository.existsById(trainerId)) { + errors.add("trainer past opponents collection contained an invalid or unknown id"); + } + } + } + + List pokemongs = trainer.getPokemongs(); + + if (pokemongs == null) { + errors.add("trainer pokemongs collection was null or invalid"); + } + else { + for (TrainerPokemong pokemong : pokemongs) { + String pokemongId = pokemong.getId(); + if (StringUtils.isBlankStringOrNull(pokemongId) || !pokemongService.existsById(pokemongId)) { + errors.add("pokemong with id " + pokemongId + " does not exist"); + } + else { + if (!pokemongService.isEvoValid(pokemongId, pokemong.getSpecies())) { + errors.add("pokemong with id " + pokemongId + " cannot be a " + + pokemong.getSpecies()); + } + Pokemong pokemongBehind = pokemongService.getOneById(pokemongId); + if (pokemong.getNickname() != null + && pokemongBehind != null + && !pokemong.getNickname() + .equals(pokemongBehind.getNickname())) + { + errors.add("pokemong with id " + pokemongId + " already has a nickname"); + } + } + } + } + + if (!errors.isEmpty()) { + throw new NonValidEntityException("Validation errors: " + String.join(", ", errors)); + } + } +} diff --git a/src/main/java/fr/uca/iut/utils/StringUtils.java b/src/main/java/fr/uca/iut/utils/StringUtils.java new file mode 100644 index 0000000..453af77 --- /dev/null +++ b/src/main/java/fr/uca/iut/utils/StringUtils.java @@ -0,0 +1,7 @@ +package fr.uca.iut.utils; + +public class StringUtils { + public static boolean isBlankStringOrNull(String string) { + return string == null || string.isBlank(); + } +} diff --git a/src/main/java/fr/uca/iut/utils/enums/MoveCategoryName.java b/src/main/java/fr/uca/iut/utils/enums/MoveCategoryName.java new file mode 100644 index 0000000..dd1466c --- /dev/null +++ b/src/main/java/fr/uca/iut/utils/enums/MoveCategoryName.java @@ -0,0 +1,7 @@ +package fr.uca.iut.utils.enums; + +public enum MoveCategoryName { + PHYSICAL, + SPECIAL, + STATUS +} diff --git a/src/main/java/fr/uca/iut/utils/PokemongName.java b/src/main/java/fr/uca/iut/utils/enums/PokemongName.java similarity index 99% rename from src/main/java/fr/uca/iut/utils/PokemongName.java rename to src/main/java/fr/uca/iut/utils/enums/PokemongName.java index dcd0723..a6296c0 100644 --- a/src/main/java/fr/uca/iut/utils/PokemongName.java +++ b/src/main/java/fr/uca/iut/utils/enums/PokemongName.java @@ -1,4 +1,4 @@ -package fr.uca.iut.utils; +package fr.uca.iut.utils.enums; public enum PokemongName { BULBASAUR, diff --git a/src/main/java/fr/uca/iut/utils/TypeName.java b/src/main/java/fr/uca/iut/utils/enums/TypeName.java similarity index 87% rename from src/main/java/fr/uca/iut/utils/TypeName.java rename to src/main/java/fr/uca/iut/utils/enums/TypeName.java index 3383d2d..757b1c6 100644 --- a/src/main/java/fr/uca/iut/utils/TypeName.java +++ b/src/main/java/fr/uca/iut/utils/enums/TypeName.java @@ -1,4 +1,4 @@ -package fr.uca.iut.utils; +package fr.uca.iut.utils.enums; public enum TypeName { NORMAL, diff --git a/src/main/java/fr/uca/iut/utils/exceptions/NonValidEntityException.java b/src/main/java/fr/uca/iut/utils/exceptions/NonValidEntityException.java new file mode 100644 index 0000000..2d0bf42 --- /dev/null +++ b/src/main/java/fr/uca/iut/utils/exceptions/NonValidEntityException.java @@ -0,0 +1,7 @@ +package fr.uca.iut.utils.exceptions; + +public class NonValidEntityException extends RuntimeException { + public NonValidEntityException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/openapi.yaml b/src/main/resources/META-INF/openapi.yaml new file mode 100644 index 0000000..b3192f0 --- /dev/null +++ b/src/main/resources/META-INF/openapi.yaml @@ -0,0 +1,1748 @@ +openapi: "3.0.3" +info: + title: "Pokemong API" + description: "Pokemong API" + version: "1.0.0" +servers: + - url: "http://localhost:8080" + +paths: + + /pokemong/{id}: + + get: + summary: Get a Pokemong by ID + parameters: + - name: id + in: path + required: true + description: The ID of the Pokemong + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Pokemong' + '400': + description: Invalid ID format + '404': + description: Entity not found for given ID + + put: + summary: Update a Pokemong by ID + parameters: + - name: id + in: path + required: true + description: The ID of the Pokemong + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Pokemong' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Pokemong' + '400': + description: Invalid ID format or Non-valid entity + '404': + description: Entity not found for given ID + + delete: + summary: Delete a Pokemong by ID + parameters: + - name: id + in: path + required: true + description: The ID of the Pokemong + schema: + type: string + responses: + '200': + description: OK + '400': + description: Invalid ID format + + /pokemong: + + get: + summary: Get all Pokemongs + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pokemong' + + post: + summary: Create a new Pokemong + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Pokemong' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/Pokemong' + '400': + description: Non-valid entity + + /move/{id}: + + get: + summary: Get a move by ID + parameters: + - name: id + in: path + required: true + description: The ID of the move + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Move' + '400': + description: Invalid ID format + '404': + description: Entity not found for given ID + + put: + summary: Update a move by ID + parameters: + - name: id + in: path + required: true + description: The ID of the move + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Move' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Move' + '400': + description: Invalid ID format or Non-valid entity + '404': + description: Entity not found for given ID + + delete: + summary: Delete a move by ID + parameters: + - name: id + in: path + required: true + description: The ID of the move + schema: + type: string + responses: + '200': + description: OK + '400': + description: Invalid ID format + + /move: + + get: + summary: Get all moves + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Move' + + post: + summary: Create a new move + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Move' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/Move' + '400': + description: Non-valid entity + + /trainer: + + get: + summary: Returns a list of all trainers + responses: + '200': + description: A list of trainers + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Trainer' + + post: + summary: Creates a new trainer + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Trainer' + responses: + '201': + description: The created trainer + content: + application/json: + schema: + $ref: '#/components/schemas/Trainer' + '400': + description: Bad request - invalid trainer data + + /trainer/{id}: + + get: + summary: Returns a trainer by ID + parameters: + - in: path + name: id + required: true + schema: + type: string + responses: + '200': + description: A single trainer + content: + application/json: + schema: + $ref: '#/components/schemas/Trainer' + '404': + description: Trainer not found + + put: + summary: Updates a trainer by ID + parameters: + - in: path + name: id + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Trainer' + responses: + '200': + description: The updated trainer + content: + application/json: + schema: + $ref: '#/components/schemas/Trainer' + '400': + description: Bad request - invalid trainer data + '404': + description: Trainer not found + + delete: + summary: Deletes a trainer by ID + parameters: + - in: path + name: id + required: true + schema: + type: string + responses: + '200': + description: Trainer successfully deleted + '400': + description: Bad request - invalid trainer ID + '404': + description: Trainer not found + +components: + + schemas: + + Pokemong: + type: object + required: + - dob + - level + - pokedexId + - evoStage + - evoTrack + - types + - moveSet + properties: + nickname: + type: string + nullable: true + dob: + type: string + format: date + level: + type: integer + minimum: 1 + pokedexId: + type: integer + minimum: 1 + evoStage: + type: integer + minimum: 0 + evoTrack: + type: array + minItems: 1 + items: + $ref: '#/components/schemas/PokemongName' + trainer: + type: string + description: MongoDB ObjectId referencing a document in a collection + nullable: true + types: + type: array + minItems: 1 + maxItems: 2 + items: + $ref: '#/components/schemas/TypeName' + moveSet: + type: array + minItems: 1 + maxItems: 4 + items: + $ref: '#/components/schemas/PokemongMove' + + Move: + type: object + required: + - name + - power + - category + - accuracy + - type + properties: + name: + type: string + minLength: 1 + category: + $ref: '#/components/schemas/MoveCategoryName' + power: + type: integer + minimum: 0 + accuracy: + type: integer + minimum: 0 + type: + $ref: '#/components/schemas/TypeName' + + Trainer: + type: object + required: + - name + - dob + - wins + - losses + - pastOpponents + - pokemongs + properties: + name: + type: string + minLength: 1 + dob: + type: string + format: date + wins: + type: integer + minimum: 0 + losses: + type: integer + minimum: 0 + pastOpponents: + type: array + items: + type: string + minLength: 1 + pokemongs: + type: array + items: + $ref: '#/components/schemas/TrainerPokemong' + + PokemongMove: + type: object + required: + - id + - name + properties: + id: + type: string + minLength: 1 + name: + type: string + minLength: 1 + + TrainerPokemong: + type: object + required: + - id + - species + properties: + id: + type: string + minLength: 1 + nickname: + type: string + nullable: true + species: + $ref: '#/components/schemas/PokemongName' + + MoveCategoryName: + type: string + enum: [ + PHYSICAL, + SPECIAL, + STATUS ] + + TypeName: + type: string + enum: [ + NORMAL, + GRASS, + ELECTRIC, + WATER, + FIRE, + BUG, + GHOST, + PSYCHIC, + STEEL, + DARK, + FLYING, + ICE, + POISON, + GROUND, + ROCK, + DRAGON, + FIGHTING, + FAIRY + ] + + PokemongName: + type: string + enum: [ + BULBASAUR, + IVYSAUR, + VENUSAUR, + CHARMANDER, + CHARMELEON, + CHARIZARD, + SQUIRTLE, + WARTORTLE, + BLASTOISE, + CATERPIE, + METAPOD, + BUTTERFREE, + WEEDLE, + KAKUNA, + BEEDRILL, + PIDGEY, + PIDGEOTTO, + PIDGEOT, + RATTATA, + RATICATE, + SPEAROW, + FEAROW, + EKANS, + ARBOK, + PIKACHU, + RAICHU, + SANDSHREW, + SANDSLASH, + NIDORAN_F, + NIDORINA, + NIDOQUEEN, + NIDORAN_M, + NIDORINO, + NIDOKING, + CLEFAIRY, + CLEFABLE, + VULPIX, + NINETALES, + JIGGLYPUFF, + WIGGLYTUFF, + ZUBAT, + GOLBAT, + ODDISH, + GLOOM, + VILEPLUME, + PARAS, + PARASECT, + VENONAT, + VENOMOTH, + DIGLETT, + DUGTRIO, + MEOWTH, + PERSIAN, + PSYDUCK, + GOLDUCK, + MANKEY, + PRIMEAPE, + GROWLITHE, + ARCANINE, + POLIWAG, + POLIWHIRL, + POLIWRATH, + ABRA, + KADABRA, + ALAKAZAM, + MACHOP, + MACHOKE, + MACHAMP, + BELLSPROUT, + WEEPINBELL, + VICTREEBEL, + TENTACOOL, + TENTACRUEL, + GEODUDE, + GRAVELER, + GOLEM, + PONYTA, + RAPIDASH, + SLOWPOKE, + SLOWBRO, + MAGNEMITE, + MAGNETON, + FARFETCHD, + DODUO, + DODRIO, + SEEL, + DEWGONG, + GRIMER, + MUK, + SHELLDER, + CLOYSTER, + GASTLY, + HAUNTER, + GENGAR, + ONIX, + DROWZEE, + HYPNO, + KRABBY, + KINGLER, + VOLTORB, + ELECTRODE, + EXEGGCUTE, + EXEGGUTOR, + CUBONE, + MAROWAK, + HITMONLEE, + HITMONCHAN, + LICKITUNG, + KOFFING, + WEEZING, + RHYHORN, + RHYDON, + CHANSEY, + TANGELA, + KANGASKHAN, + HORSEA, + SEADRA, + GOLDEEN, + SEAKING, + STARYU, + STARMIE, + MR_MIME, + SCYTHER, + JYNX, + ELECTABUZZ, + MAGMAR, + PINSIR, + TAUROS, + MAGIKARP, + GYARADOS, + LAPRAS, + DITTO, + EEVEE, + VAPOREON, + JOLTEON, + FLAREON, + PORYGON, + OMANYTE, + OMASTAR, + KABUTO, + KABUTOPS, + AERODACTYL, + SNORLAX, + ARTICUNO, + ZAPDOS, + MOLTRES, + DRATINI, + DRAGONAIR, + DRAGONITE, + MEWTWO, + MEW, + CHIKORITA, + BAYLEEF, + MEGANIUM, + CYNDAQUIL, + QUILAVA, + TYPHLOSION, + TOTODILE, + CROCONAW, + FERALIGATR, + SENTRET, + FURRET, + HOOTHOOT, + NOCTOWL, + LEDYBA, + LEDIAN, + SPINARAK, + ARIADOS, + CROBAT, + CHINCHOU, + LANTURN, + PICHU, + CLEFFA, + IGGLYBUFF, + TOGEPI, + TOGETIC, + NATU, + XATU, + MAREEP, + FLAAFFY, + AMPHAROS, + BELLOSSOM, + MARILL, + AZUMARILL, + SUDOWOODO, + POLITOED, + HOPPIP, + SKIPLOOM, + JUMPLUFF, + AIPOM, + SUNKERN, + SUNFLORA, + YANMA, + WOOPER, + QUAGSIRE, + ESPEON, + UMBREON, + MURKROW, + SLOWKING, + MISDREAVUS, + UNOWN, + WOBBUFFET, + GIRAFARIG, + PINECO, + FORRETRESS, + DUNSPARCE, + GLIGAR, + STEELIX, + SNUBBULL, + GRANBULL, + QWILFISH, + SCIZOR, + SHUCKLE, + HERACROSS, + SNEASEL, + TEDDIURSA, + URSARING, + SLUGMA, + MAGCARGO, + SWINUB, + PILOSWINE, + CORSOLA, + REMORAID, + OCTILLERY, + DELIBIRD, + MANTINE, + SKARMORY, + HOUNDOUR, + HOUNDOOM, + KINGDRA, + PHANPY, + DONPHAN, + PORYGON2, + STANTLER, + SMEARGLE, + TYROGUE, + HITMONTOP, + SMOOCHUM, + ELEKID, + MAGBY, + MILTANK, + BLISSEY, + RAIKOU, + ENTEI, + SUICUNE, + LARVITAR, + PUPITAR, + TYRANITAR, + LUGIA, + HO_OH, + CELEBI, + TREECKO, + GROVYLE, + SCEPTILE, + TORCHIC, + COMBUSKEN, + BLAZIKEN, + MUDKIP, + MARSHTOMP, + SWAMPERT, + POOCHYENA, + MIGHTYENA, + ZIGZAGOON, + LINOONE, + WURMPLE, + SILCOON, + BEAUTIFLY, + CASCOON, + DUSTOX, + LOTAD, + LOMBRE, + LUDICOLO, + SEEDOT, + NUZLEAF, + SHIFTRY, + TAILLOW, + SWELLOW, + WINGULL, + PELIPPER, + RALTS, + KIRLIA, + GARDEVOIR, + SURSKIT, + MASQUERAIN, + SHROOMISH, + BRELOOM, + SLAKOTH, + VIGOROTH, + SLAKING, + NINCADA, + NINJASK, + SHEDINJA, + WHISMUR, + LOUDRED, + EXPLOUD, + MAKUHITA, + HARIYAMA, + AZURILL, + NOSEPASS, + SKITTY, + DELCATTY, + SABLEYE, + MAWILE, + ARON, + LAIRON, + AGGRON, + MEDITITE, + MEDICHAM, + ELECTRIKE, + MANECTRIC, + PLUSLE, + MINUN, + VOLBEAT, + ILLUMISE, + ROSELIA, + GULPIN, + SWALOT, + CARVANHA, + SHARPEDO, + WAILMER, + WAILORD, + NUMEL, + CAMERUPT, + TORKOAL, + SPOINK, + GRUMPIG, + SPINDA, + TRAPINCH, + VIBRAVA, + FLYGON, + CACNEA, + CACTURNE, + SWABLU, + ALTARIA, + ZANGOOSE, + SEVIPER, + LUNATONE, + SOLROCK, + BARBOACH, + WHISCASH, + CORPHISH, + CRAWDAUNT, + BALTOY, + CLAYDOL, + LILEEP, + CRADILY, + ANORITH, + ARMALDO, + FEEBAS, + MILOTIC, + CASTFORM, + KECLEON, + SHUPPET, + BANETTE, + DUSKULL, + DUSCLOPS, + TROPIUS, + CHIMECHO, + ABSOL, + WYNAUT, + SNORUNT, + GLALIE, + SPHEAL, + SEALEO, + WALREIN, + CLAMPERL, + HUNTAIL, + GOREBYSS, + RELICANTH, + LUVDISC, + BAGON, + SHELGON, + SALAMENCE, + BELDUM, + METANG, + METAGROSS, + REGIROCK, + REGICE, + REGISTEEL, + LATIAS, + LATIOS, + KYOGRE, + GROUDON, + RAYQUAZA, + JIRACHI, + DEOXYS_NORMAL, + TURTWIG, + GROTLE, + TORTERRA, + CHIMCHAR, + MONFERNO, + INFERNAPE, + PIPLUP, + PRINPLUP, + EMPOLEON, + STARLY, + STARAVIA, + STARAPTOR, + BIDOOF, + BIBAREL, + KRICKETOT, + KRICKETUNE, + SHINX, + LUXIO, + LUXRAY, + BUDEW, + ROSERADE, + CRANIDOS, + RAMPARDOS, + SHIELDON, + BASTIODON, + BURMY, + WORMADAM_PLANT, + MOTHIM, + COMBEE, + VESPIQUEN, + PACHIRISU, + BUIZEL, + FLOATZEL, + CHERUBI, + CHERRIM, + SHELLOS, + GASTRODON, + AMBIPOM, + DRIFLOON, + DRIFBLIM, + BUNEARY, + LOPUNNY, + MISMAGIUS, + HONCHKROW, + GLAMEOW, + PURUGLY, + CHINGLING, + STUNKY, + SKUNTANK, + BRONZOR, + BRONZONG, + BONSLY, + MIME_JR, + HAPPINY, + CHATOT, + SPIRITOMB, + GIBLE, + GABITE, + GARCHOMP, + MUNCHLAX, + RIOLU, + LUCARIO, + HIPPOPOTAS, + HIPPOWDON, + SKORUPI, + DRAPION, + CROAGUNK, + TOXICROAK, + CARNIVINE, + FINNEON, + LUMINEON, + MANTYKE, + SNOVER, + ABOMASNOW, + WEAVILE, + MAGNEZONE, + LICKILICKY, + RHYPERIOR, + TANGROWTH, + ELECTIVIRE, + MAGMORTAR, + TOGEKISS, + YANMEGA, + LEAFEON, + GLACEON, + GLISCOR, + MAMOSWINE, + PORYGON_Z, + GALLADE, + PROBOPASS, + DUSKNOIR, + FROSLASS, + ROTOM, + UXIE, + MESPRIT, + AZELF, + DIALGA, + PALKIA, + HEATRAN, + REGIGIGAS, + GIRATINA_ALTERED, + CRESSELIA, + PHIONE, + MANAPHY, + DARKRAI, + SHAYMIN_LAND, + ARCEUS, + VICTINI, + SNIVY, + SERVINE, + SERPERIOR, + TEPIG, + PIGNITE, + EMBOAR, + OSHAWOTT, + DEWOTT, + SAMUROTT, + PATRAT, + WATCHOG, + LILLIPUP, + HERDIER, + STOUTLAND, + PURRLOIN, + LIEPARD, + PANSAGE, + SIMISAGE, + PANSEAR, + SIMISEAR, + PANPOUR, + SIMIPOUR, + MUNNA, + MUSHARNA, + PIDOVE, + TRANQUILL, + UNFEZANT, + BLITZLE, + ZEBSTRIKA, + ROGGENROLA, + BOLDORE, + GIGALITH, + WOOBAT, + SWOOBAT, + DRILBUR, + EXCADRILL, + AUDINO, + TIMBURR, + GURDURR, + CONKELDURR, + TYMPOLE, + PALPITOAD, + SEISMITOAD, + THROH, + SAWK, + SEWADDLE, + SWADLOON, + LEAVANNY, + VENIPEDE, + WHIRLIPEDE, + SCOLIPEDE, + COTTONEE, + WHIMSICOTT, + PETILIL, + LILLIGANT, + BASCULIN_RED_STRIPED, + SANDILE, + KROKOROK, + KROOKODILE, + DARUMAKA, + DARMANITAN_STANDARD, + MARACTUS, + DWEBBLE, + CRUSTLE, + SCRAGGY, + SCRAFTY, + SIGILYPH, + YAMASK, + COFAGRIGUS, + TIRTOUGA, + CARRACOSTA, + ARCHEN, + ARCHEOPS, + TRUBBISH, + GARBODOR, + ZORUA, + ZOROARK, + MINCCINO, + CINCCINO, + GOTHITA, + GOTHORITA, + GOTHITELLE, + SOLOSIS, + DUOSION, + REUNICLUS, + DUCKLETT, + SWANNA, + VANILLITE, + VANILLISH, + VANILLUXE, + DEERLING, + SAWSBUCK, + EMOLGA, + KARRABLAST, + ESCAVALIER, + FOONGUS, + AMOONGUSS, + FRILLISH, + JELLICENT, + ALOMOMOLA, + JOLTIK, + GALVANTULA, + FERROSEED, + FERROTHORN, + KLINK, + KLANG, + KLINKLANG, + TYNAMO, + EELEKTRIK, + EELEKTROSS, + ELGYEM, + BEHEEYEM, + LITWICK, + LAMPENT, + CHANDELURE, + AXEW, + FRAXURE, + HAXORUS, + CUBCHOO, + BEARTIC, + CRYOGONAL, + SHELMET, + ACCELGOR, + STUNFISK, + MIENFOO, + MIENSHAO, + DRUDDIGON, + GOLETT, + GOLURK, + PAWNIARD, + BISHARP, + BOUFFALANT, + RUFFLET, + BRAVIARY, + VULLABY, + MANDIBUZZ, + HEATMOR, + DURANT, + DEINO, + ZWEILOUS, + HYDREIGON, + LARVESTA, + VOLCARONA, + COBALION, + TERRAKION, + VIRIZION, + TORNADUS_INCARNATE, + THUNDURUS_INCARNATE, + RESHIRAM, + ZEKROM, + LANDORUS_INCARNATE, + KYUREM, + KELDEO_ORDINARY, + MELOETTA_ARIA, + GENESECT, + CHESPIN, + QUILLADIN, + CHESNAUGHT, + FENNEKIN, + BRAIXEN, + DELPHOX, + FROAKIE, + FROGADIER, + GRENINJA, + BUNNELBY, + DIGGERSBY, + FLETCHLING, + FLETCHINDER, + TALONFLAME, + SCATTERBUG, + SPEWPA, + VIVILLON, + LITLEO, + PYROAR, + FLABEBE, + FLOETTE, + FLORGES, + SKIDDO, + GOGOAT, + PANCHAM, + PANGORO, + FURFROU, + ESPURR, + MEOWSTIC_MALE, + HONEDGE, + DOUBLADE, + AEGISLASH_SHIELD, + SPRITZEE, + AROMATISSE, + SWIRLIX, + SLURPUFF, + INKAY, + MALAMAR, + BINACLE, + BARBARACLE, + SKRELP, + DRAGALGE, + CLAUNCHER, + CLAWITZER, + HELIOPTILE, + HELIOLISK, + TYRUNT, + TYRANTRUM, + AMAURA, + AURORUS, + SYLVEON, + HAWLUCHA, + DEDENNE, + CARBINK, + GOOMY, + SLIGGOO, + GOODRA, + KLEFKI, + PHANTUMP, + TREVENANT, + PUMPKABOO_AVERAGE, + GOURGEIST_AVERAGE, + BERGMITE, + AVALUGG, + NOIBAT, + NOIVERN, + XERNEAS, + YVELTAL, + ZYGARDE_50, + DIANCIE, + HOOPA, + VOLCANION, + ROWLET, + DARTRIX, + DECIDUEYE, + LITTEN, + TORRACAT, + INCINEROAR, + POPPLIO, + BRIONNE, + PRIMARINA, + PIKIPEK, + TRUMBEAK, + TOUCANNON, + YUNGOOS, + GUMSHOOS, + GRUBBIN, + CHARJABUG, + VIKAVOLT, + CRABRAWLER, + CRABOMINABLE, + ORICORIO_BAILE, + CUTIEFLY, + RIBOMBEE, + ROCKRUFF, + LYCANROC_MIDDAY, + WISHIWASHI_SOLO, + MAREANIE, + TOXAPEX, + MUDBRAY, + MUDSDALE, + DEWPIDER, + ARAQUANID, + FOMANTIS, + LURANTIS, + MORELULL, + SHIINOTIC, + SALANDIT, + SALAZZLE, + STUFFUL, + BEWEAR, + BOUNSWEET, + STEENEE, + TSAREENA, + COMFEY, + ORANGURU, + PASSIMIAN, + WIMPOD, + GOLISOPOD, + SANDYGAST, + PALOSSAND, + PYUKUMUKU, + TYPE_NULL, + SILVALLY, + MINIOR_RED_METEOR, + KOMALA, + TURTONATOR, + TOGEDEMARU, + MIMIKYU_DISGUISED, + BRUXISH, + DRAMPA, + DHELMISE, + JANGMO_O, + HAKAMO_O, + KOMMO_O, + TAPU_KOKO, + TAPU_LELE, + TAPU_BULU, + TAPU_FINI, + COSMOG, + COSMOEM, + SOLGALEO, + LUNALA, + NIHILEGO, + BUZZWOLE, + PHEROMOSA, + XURKITREE, + CELESTEELA, + KARTANA, + GUZZLORD, + NECROZMA, + MAGEARNA, + MARSHADOW, + POIPOLE, + NAGANADEL, + STAKATAKA, + BLACEPHALON, + ZERAORA, + MELTAN, + MELMETAL, + GROOKEY, + THWACKEY, + RILLABOOM, + SCORBUNNY, + RABOOT, + CINDERACE, + SOBBLE, + DRIZZILE, + INTELEON, + SKWOVET, + GREEDENT, + ROOKIDEE, + CORVISQUIRE, + CORVIKNIGHT, + BLIPBUG, + DOTTLER, + ORBEETLE, + NICKIT, + THIEVUL, + GOSSIFLEUR, + ELDEGOSS, + WOOLOO, + DUBWOOL, + CHEWTLE, + DREDNAW, + YAMPER, + BOLTUND, + ROLYCOLY, + CARKOL, + COALOSSAL, + APPLIN, + FLAPPLE, + APPLETUN, + SILICOBRA, + SANDACONDA, + CRAMORANT, + ARROKUDA, + BARRASKEWDA, + TOXEL, + TOXTRICITY_AMPED, + SIZZLIPEDE, + CENTISKORCH, + CLOBBOPUS, + GRAPPLOCT, + SINISTEA, + POLTEAGEIST, + HATENNA, + HATTREM, + HATTERENE, + IMPIDIMP, + MORGREM, + GRIMMSNARL, + OBSTAGOON, + PERRSERKER, + CURSOLA, + SIRFETCHD, + MR_RIME, + RUNERIGUS, + MILCERY, + ALCREMIE, + FALINKS, + PINCURCHIN, + SNOM, + FROSMOTH, + STONJOURNER, + EISCUE_ICE, + INDEEDEE_MALE, + MORPEKO_FULL_BELLY, + CUFANT, + COPPERAJAH, + DRACOZOLT, + ARCTOZOLT, + DRACOVISH, + ARCTOVISH, + DURALUDON, + DREEPY, + DRAKLOAK, + DRAGAPULT, + ZACIAN, + ZAMAZENTA, + ETERNATUS, + KUBFU, + URSHIFU_SINGLE_STRIKE, + ZARUDE, + REGIELEKI, + REGIDRAGO, + GLASTRIER, + SPECTRIER, + CALYREX, + WYRDEER, + KLEAVOR, + URSALUNA, + BASCULEGION_MALE, + SNEASLER, + OVERQWIL, + ENAMORUS_INCARNATE, + SPRIGATITO, + FLORAGATO, + MEOWSCARADA, + FUECOCO, + CROCALOR, + SKELEDIRGE, + QUAXLY, + QUAXWELL, + QUAQUAVAL, + LECHONK, + OINKOLOGNE, + TAROUNTULA, + SPIDOPS, + NYMBLE, + LOKIX, + PAWMI, + PAWMO, + PAWMOT, + TANDEMAUS, + MAUSHOLD, + FIDOUGH, + DACHSBUN, + SMOLIV, + DOLLIV, + ARBOLIVA, + SQUAWKABILLY, + NACLI, + NACLSTACK, + GARGANACL, + CHARCADET, + ARMAROUGE, + CERULEDGE, + TADBULB, + BELLIBOLT, + WATTREL, + KILOWATTREL, + MASCHIFF, + MABOSSTIFF, + SHROODLE, + GRAFAIAI, + BRAMBLIN, + BRAMBLEGHAST, + TOEDSCOOL, + TOEDSCRUEL, + KLAWF, + CAPSAKID, + SCOVILLAIN, + RELLOR, + RABSCA, + FLITTLE, + ESPATHRA, + TINKATINK, + TINKATUFF, + TINKATON, + WIGLETT, + WUGTRIO, + BOMBIRDIER, + FINIZEN, + PALAFIN, + VAROOM, + REVAVROOM, + CYCLIZAR, + ORTHWORM, + GLIMMET, + GLIMMORA, + GREAVARD, + HOUNDSTONE, + FLAMIGO, + CETODDLE, + CETITAN, + VELUZA, + DONDOZO, + TATSUGIRI, + ANNIHILAPE, + CLODSIRE, + FARIGIRAF, + DUDUNSPARCE, + KINGAMBIT, + GREAT_TUSK, + SCREAM_TAIL, + BRUTE_BONNET, + FLUTTER_MANE, + SLITHER_WING, + SANDY_SHOCKS, + IRON_TREADS, + IRON_BUNDLE, + IRON_HANDS, + IRON_JUGULIS, + IRON_MOTH, + IRON_THORNS, + FRIGIBAX, + ARCTIBAX, + BAXCALIBUR, + GIMMIGHOUL, + GHOLDENGO, + WO_CHIEN, + CHIEN_PAO, + TING_LU, + CHI_YU, + ROARING_MOON, + IRON_VALIANT, + KORAIDON, + MIRAIDON, + WALKING_WAKE, + IRON_LEAVES, + DEOXYS_ATTACK, + DEOXYS_DEFENSE, + DEOXYS_SPEED, + WORMADAM_SANDY, + WORMADAM_TRASH, + SHAYMIN_SKY, + GIRATINA_ORIGIN, + ROTOM_HEAT, + ROTOM_WASH, + ROTOM_FROST, + ROTOM_FAN, + ROTOM_MOW, + CASTFORM_SUNNY, + CASTFORM_RAINY, + CASTFORM_SNOWY, + BASCULIN_BLUE_STRIPED, + DARMANITAN_ZEN, + MELOETTA_PIROUETTE, + TORNADUS_THERIAN, + THUNDURUS_THERIAN, + LANDORUS_THERIAN, + KYUREM_BLACK, + KYUREM_WHITE, + KELDEO_RESOLUTE, + MEOWSTIC_FEMALE, + AEGISLASH_BLADE, + PUMPKABOO_SMALL, + PUMPKABOO_LARGE, + PUMPKABOO_SUPER, + GOURGEIST_SMALL, + GOURGEIST_LARGE, + GOURGEIST_SUPER, + VENUSAUR_MEGA, + CHARIZARD_MEGA_X, + CHARIZARD_MEGA_Y, + BLASTOISE_MEGA, + ALAKAZAM_MEGA, + GENGAR_MEGA, + KANGASKHAN_MEGA, + PINSIR_MEGA, + GYARADOS_MEGA, + AERODACTYL_MEGA, + MEWTWO_MEGA_X, + MEWTWO_MEGA_Y, + AMPHAROS_MEGA, + SCIZOR_MEGA, + HERACROSS_MEGA, + HOUNDOOM_MEGA, + TYRANITAR_MEGA, + BLAZIKEN_MEGA, + GARDEVOIR_MEGA, + MAWILE_MEGA, + AGGRON_MEGA, + MEDICHAM_MEGA, + MANECTRIC_MEGA, + BANETTE_MEGA, + ABSOL_MEGA, + GARCHOMP_MEGA, + LUCARIO_MEGA, + ABOMASNOW_MEGA, + FLOETTE_ETERNAL, + LATIAS_MEGA, + LATIOS_MEGA, + SWAMPERT_MEGA, + SCEPTILE_MEGA, + SABLEYE_MEGA, + ALTARIA_MEGA, + GALLADE_MEGA, + AUDINO_MEGA, + SHARPEDO_MEGA, + SLOWBRO_MEGA, + STEELIX_MEGA, + PIDGEOT_MEGA, + GLALIE_MEGA, + DIANCIE_MEGA, + METAGROSS_MEGA, + KYOGRE_PRIMAL, + GROUDON_PRIMAL, + RAYQUAZA_MEGA, + PIKACHU_ROCK_STAR, + PIKACHU_BELLE, + PIKACHU_POP_STAR, + PIKACHU_PHD, + PIKACHU_LIBRE, + PIKACHU_COSPLAY, + HOOPA_UNBOUND, + CAMERUPT_MEGA, + LOPUNNY_MEGA, + SALAMENCE_MEGA, + BEEDRILL_MEGA, + RATTATA_ALOLA, + RATICATE_ALOLA, + RATICATE_TOTEM_ALOLA, + PIKACHU_ORIGINAL_CAP, + PIKACHU_HOENN_CAP, + PIKACHU_SINNOH_CAP, + PIKACHU_UNOVA_CAP, + PIKACHU_KALOS_CAP, + PIKACHU_ALOLA_CAP, + RAICHU_ALOLA, + SANDSHREW_ALOLA, + SANDSLASH_ALOLA, + VULPIX_ALOLA, + NINETALES_ALOLA, + DIGLETT_ALOLA, + DUGTRIO_ALOLA, + MEOWTH_ALOLA, + PERSIAN_ALOLA, + GEODUDE_ALOLA, + GRAVELER_ALOLA, + GOLEM_ALOLA, + GRIMER_ALOLA, + MUK_ALOLA, + EXEGGUTOR_ALOLA, + MAROWAK_ALOLA, + GRENINJA_BATTLE_BOND, + GRENINJA_ASH, + ZYGARDE_10_POWER_CONSTRUCT, + ZYGARDE_50_POWER_CONSTRUCT, + ZYGARDE_COMPLETE, + GUMSHOOS_TOTEM, + VIKAVOLT_TOTEM, + ORICORIO_POM_POM, + ORICORIO_PAU, + ORICORIO_SENSU, + LYCANROC_MIDNIGHT, + WISHIWASHI_SCHOOL, + LURANTIS_TOTEM, + SALAZZLE_TOTEM, + MINIOR_ORANGE_METEOR, + MINIOR_YELLOW_METEOR, + MINIOR_GREEN_METEOR, + MINIOR_BLUE_METEOR, + MINIOR_INDIGO_METEOR, + MINIOR_VIOLET_METEOR, + MINIOR_RED, + MINIOR_ORANGE, + MINIOR_YELLOW, + MINIOR_GREEN, + MINIOR_BLUE, + MINIOR_INDIGO, + MINIOR_VIOLET, + MIMIKYU_BUSTED, + MIMIKYU_TOTEM_DISGUISED, + MIMIKYU_TOTEM_BUSTED, + KOMMO_O_TOTEM, + MAGEARNA_ORIGINAL, + PIKACHU_PARTNER_CAP, + MAROWAK_TOTEM, + RIBOMBEE_TOTEM, + ROCKRUFF_OWN_TEMPO, + LYCANROC_DUSK, + ARAQUANID_TOTEM, + TOGEDEMARU_TOTEM, + NECROZMA_DUSK, + NECROZMA_DAWN, + NECROZMA_ULTRA, + PIKACHU_STARTER, + EEVEE_STARTER, + PIKACHU_WORLD_CAP, + MEOWTH_GALAR, + PONYTA_GALAR, + RAPIDASH_GALAR, + SLOWPOKE_GALAR, + SLOWBRO_GALAR, + FARFETCHD_GALAR, + WEEZING_GALAR, + MR_MIME_GALAR, + ARTICUNO_GALAR, + ZAPDOS_GALAR, + MOLTRES_GALAR, + SLOWKING_GALAR, + CORSOLA_GALAR, + ZIGZAGOON_GALAR, + LINOONE_GALAR, + DARUMAKA_GALAR, + DARMANITAN_GALAR_STANDARD, + DARMANITAN_GALAR_ZEN, + YAMASK_GALAR, + STUNFISK_GALAR, + ZYGARDE_10, + CRAMORANT_GULPING, + CRAMORANT_GORGING, + TOXTRICITY_LOW_KEY, + EISCUE_NOICE, + INDEEDEE_FEMALE, + MORPEKO_HANGRY, + ZACIAN_CROWNED, + ZAMAZENTA_CROWNED, + ETERNATUS_ETERNAMAX, + URSHIFU_RAPID_STRIKE, + ZARUDE_DADA, + CALYREX_ICE, + CALYREX_SHADOW, + VENUSAUR_GMAX, + CHARIZARD_GMAX, + BLASTOISE_GMAX, + BUTTERFREE_GMAX, + PIKACHU_GMAX, + MEOWTH_GMAX, + MACHAMP_GMAX, + GENGAR_GMAX, + KINGLER_GMAX, + LAPRAS_GMAX, + EEVEE_GMAX, + SNORLAX_GMAX, + GARBODOR_GMAX, + MELMETAL_GMAX, + RILLABOOM_GMAX, + CINDERACE_GMAX, + INTELEON_GMAX, + CORVIKNIGHT_GMAX, + ORBEETLE_GMAX, + DREDNAW_GMAX, + COALOSSAL_GMAX, + FLAPPLE_GMAX, + APPLETUN_GMAX, + SANDACONDA_GMAX, + TOXTRICITY_AMPED_GMAX, + CENTISKORCH_GMAX, + HATTERENE_GMAX, + GRIMMSNARL_GMAX, + ALCREMIE_GMAX, + COPPERAJAH_GMAX, + DURALUDON_GMAX, + URSHIFU_SINGLE_STRIKE_GMAX, + URSHIFU_RAPID_STRIKE_GMAX, + TOXTRICITY_LOW_KEY_GMAX, + GROWLITHE_HISUI, + ARCANINE_HISUI, + VOLTORB_HISUI, + ELECTRODE_HISUI, + TYPHLOSION_HISUI, + QWILFISH_HISUI, + SNEASEL_HISUI, + SAMUROTT_HISUI, + LILLIGANT_HISUI, + ZORUA_HISUI, + ZOROARK_HISUI, + BRAVIARY_HISUI, + SLIGGOO_HISUI, + GOODRA_HISUI, + AVALUGG_HISUI, + DECIDUEYE_HISUI, + DIALGA_ORIGIN, + PALKIA_ORIGIN, + BASCULIN_WHITE_STRIPED, + BASCULEGION_FEMALE, + ENAMORUS_THERIAN, + TAUROS_PALDEA_COMBAT_BREED, + TAUROS_PALDEA_BLAZE_BREED, + TAUROS_PALDEA_AQUA_BREED, + WOOPER_PALDEA, + OINKOLOGNE_FEMALE, + DUDUNSPARCE_THREE_SEGMENT, + PALAFIN_HERO, + MAUSHOLD_FAMILY_OF_THREE, + TATSUGIRI_DROOPY, + TATSUGIRI_STRETCHY, + SQUAWKABILLY_BLUE_PLUMAGE, + SQUAWKABILLY_YELLOW_PLUMAGE, + SQUAWKABILLY_WHITE_PLUMAGE, + GIMMIGHOUL_ROAMING, + KORAIDON_LIMITED_BUILD, + KORAIDON_SPRINTING_BUILD, + KORAIDON_SWIMMING_BUILD, + KORAIDON_GLIDING_BUILD, + MIRAIDON_LOW_POWER_MODE, + MIRAIDON_DRIVE_MODE, + MIRAIDON_AQUATIC_MODE, + MIRAIDON_GLIDE_MODE + ] \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6ba7462..008c4b3 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,3 @@ -quarkus.mongodb.connection-string=mongodb+srv://:@..mongodb.net -quarkus.mongodb.database= \ No newline at end of file +quarkus.mongodb.connection-string=mongodb+srv://:@..mongodb.net +quarkus.mongodb.database= +quarkus.smallrye-openapi.path=META-INF/openapi.yaml