🗃️ 👔 Establish a basic backend (#4)

including: persistence, business logic, OpenAPI specs
Co-authored-by: alexis.drai@etu.uca.fr <alexis.drai@etu.uca.fr>
Reviewed-on: alexis.drai/AD_MongoDB#4
pull/10/head
Alexis Drai 2 years ago committed by alexis.drai@etu.uca.fr
parent 36dd8f46da
commit 5a9afb7437

4
.gitignore vendored

@ -81,3 +81,7 @@ gradle-app.setting
# Cache of project
.gradletasknamecache
# Others
docs/todos.md
/src/test/resources/application.properties

@ -13,16 +13,12 @@ The application is developed using the Quarkus framework and uses `MongoDB` as i
This application is a RESTful service designed to emulate a basic Pokemong management system. It allows users to perform
CRUD operations on Pokemongs, trainers, moves, and types.
<details><summary>🗂️ See the DCM</summary>
### 🗂️ DCM
<img src="./docs/mcd.png" alt="Data Concept Model" title="Data Concept Model">
</details>
<details><summary>🧬 See the UML Class diagram</summary>
### 🧬 UML Class diagram
<img src="./docs/nosql_uml.png" alt="UML Class Diagram" title="UML Class Diagram">
</details>
## Prep steps
### ♨️ Java version
@ -83,10 +79,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)

@ -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'
}

@ -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"
- 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")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

@ -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<T extends GenericEntity> implements CollectibleCodec<T> {
private final Codec<Document> documentCodec;
protected GenericCodec() {
this.documentCodec = MongoClientSettings.getDefaultCodecRegistry()
.get(Document.class);
}
public Codec<Document> getDocumentCodec() {
return documentCodec;
}
@Override
public abstract void encode(BsonWriter writer, T entity, EncoderContext encoderContext);
@Override
public abstract Class<T> 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<T extends GenericEntity> implements CollectibleCodec<T> {
@Override
public abstract void encode(BsonWriter writer, T entity, EncoderContext encoderContext);
@Override
public abstract Class<T> 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);
}

@ -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<Move> {
private final Codec<Document> 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<Move> 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;
}
}

@ -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 <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) {
if (clazz.equals(Move.class)) {
return (Codec<T>) new MoveCodec();
}
return null;
}
}

@ -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<Pokemong> {
private final Codec<Document> 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<String> 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<Document> types = Optional.ofNullable(pokemong.getTypes())
.orElse(Collections.emptyList())
.stream()
.map(type -> {
Document typeDoc = new Document();
typeDoc.put("name",
type.getName()
.name());
List<String> weakAgainst = type.getWeakAgainst()
.stream()
.map(Enum::name)
.collect(Collectors.toList());
typeDoc.put("weakAgainst", weakAgainst);
List<String> 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<Pokemong> 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<PokemongName> evoTrack = Optional.ofNullable((List<String>) 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<Type> types = Optional.ofNullable((List<Document>) document.get("types"))
.orElse(Collections.emptyList())
.stream()
.map(typeDoc -> {
Type type = new Type();
type.setName(TypeName.valueOf(typeDoc.getString("name")));
List<TypeName> weakAgainst = Optional
.ofNullable((List<String>) typeDoc.get("weakAgainst"))
.orElse(Collections.emptyList())
.stream()
.map(TypeName::valueOf)
.collect(Collectors.toList());
type.setWeakAgainst(weakAgainst);
List<TypeName> effectiveAgainst = Optional
.ofNullable((List<String>) 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<Pokemong> {
private final Codec<Document> 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<String> 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<Document> types = pokemong.getTypes()
.stream()
.map(type -> {
Document typeDoc = new Document();
typeDoc.put("name",
type.getName()
.name());
List<String> weakAgainst = type.getWeakAgainst()
.stream()
.map(Enum::name)
.collect(Collectors.toList());
typeDoc.put("weakAgainst", weakAgainst);
List<String> effectiveAgainst = type.getEffectiveAgainst()
.stream()
.map(Enum::name)
.collect(Collectors.toList());
typeDoc.put("effectiveAgainst", effectiveAgainst);
return typeDoc;
})
.collect(Collectors.toList());
doc.put("types", types);
List<Document> 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<Pokemong> 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<PokemongName> evoTrack = document.getList("evoTrack", String.class)
.stream()
.map(PokemongName::valueOf)
.collect(Collectors.toList());
pokemong.setEvoTrack(evoTrack);
pokemong.setTrainer(document.getObjectId("trainer")
.toString());
List<Type> types = document.getList("types", Document.class)
.stream()
.map(TypeCodecUtil::extractType)
.collect(Collectors.toList());
pokemong.setTypes(types);
Set<PokemongMove> 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;
}
}

@ -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 <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) {
if (clazz.equals(Pokemong.class)) {
return (Codec<T>) 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 <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) {
if (clazz.equals(Pokemong.class)) {
return (Codec<T>) new PokemongCodec();
}
return null;
}
}

@ -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<Trainer> {
private final Codec<Document> 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<Trainer> 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<Trainer> {
private final Codec<Document> 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<ObjectId> pastOpponentsIds = trainer.getPastOpponents()
.stream()
.map(ObjectId::new)
.collect(Collectors.toList());
doc.put("pastOpponents", pastOpponentsIds);
List<Document> 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<Trainer> 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<String> pastOpponentsIds = document.getList("pastOpponents", ObjectId.class)
.stream()
.map(ObjectId::toString)
.collect(Collectors.toList());
trainer.setPastOpponents(pastOpponentsIds);
List<TrainerPokemong> 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;
}
}

@ -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 <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) {
if (clazz.equals(Trainer.class)) {
return (Codec<T>) 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 <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) {
if (clazz.equals(Trainer.class)) {
return (Codec<T>) new TrainerCodec();
}
return 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<Type> {
private final Codec<Document> 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<Type> 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<String>) obj).stream().map(TypeName::valueOf).collect(Collectors.toList()))
.ifPresent(type::setWeakAgainst);
Optional.ofNullable(document.get("effectiveAgainst"))
.filter(obj -> obj instanceof List<?>)
.map(obj -> ((List<String>) obj).stream().map(TypeName::valueOf).collect(Collectors.toList()))
.ifPresent(type::setEffectiveAgainst);
return type;
}
}

@ -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 <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) {
if (clazz.equals(Type.class)) {
return (Codec<T>) new TypeCodec();
}
return null;
}
}

@ -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<TypeName> weakAgainst = typeDoc.getList("weakAgainst", String.class)
.stream()
.map(TypeName::valueOf)
.collect(Collectors.toList());
type.setWeakAgainst(weakAgainst);
List<TypeName> effectiveAgainst = typeDoc.getList("effectiveAgainst",
String.class)
.stream()
.map(TypeName::valueOf)
.collect(Collectors.toList());
type.setEffectiveAgainst(effectiveAgainst);
return type;
}
}

@ -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<T extends GenericEntity> {
protected GenericService<T> service;
public void setService(GenericService<T> 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();
}
}
}

@ -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<Move> {
@Inject
MoveService moveService;
@PostConstruct
public void init() {
setService(moveService);
}
}

@ -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<Pokemong> {
@Inject
PokemongService pokemongService;
@PostConstruct
public void init() {
setService(pokemongService);
}
}

@ -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<Trainer> {
@Inject
TrainerService trainerService;
@PostConstruct
public void init() {
setService(trainerService);
}
}

@ -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);
}
}

@ -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;
}
}

@ -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<PokemongName> evoTrack;
private Boolean isMegaEvolved;
private ObjectId trainer;
private List<Type> types; // TODO Bound this within [1;2] (in controller)
private List<ObjectId> 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<PokemongName> getEvoTrack() {
return evoTrack;
}
public void setEvoTrack(List<PokemongName> 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<Type> getTypes() {
return types;
}
public void setTypes(List<Type> types) {
this.types = types;
}
public List<ObjectId> getMoveSet() {
return moveSet;
}
public void setMoveSet(List<ObjectId> 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<PokemongName> evoTrack;
@Nullable
private String trainer;
private List<Type> types;
/**
* pokemong.moveSet: [{_id: ObjectId, name: String}]
*/
private Set<PokemongMove> 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<Type> getTypes() {
return Collections.unmodifiableList(types);
}
public void setTypes(List<Type> types) {
this.types = types;
}
public Set<PokemongMove> getMoveSet() {
return Collections.unmodifiableSet(moveSet);
}
public void setMoveSet(Set<PokemongMove> 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<PokemongName> getEvoTrack() {
return evoTrack;
}
public Integer getEvoStage() {
return evoStage;
}
public void setEvoStage(Integer evoStage) {
this.evoStage = evoStage;
}
public void setEvoTrack(List<PokemongName> evoTrack) {
this.evoTrack = evoTrack;
}
}

@ -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;
}
}

@ -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<ObjectId> pastOpponents;
private List<ObjectId> 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<ObjectId> getPastOpponents() {
return pastOpponents;
}
public void setPastOpponents(List<ObjectId> pastOpponents) {
this.pastOpponents = pastOpponents;
}
public List<ObjectId> getPokemongs() {
return pokemongs;
}
public void setPokemongs(List<ObjectId> 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<String> pastOpponents;
private List<TrainerPokemong> 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<String> getPastOpponents() {
return Collections.unmodifiableList(pastOpponents);
}
public void setPastOpponents(List<String> pastOpponents) {
this.pastOpponents = pastOpponents;
}
public List<TrainerPokemong> getPokemongs() {
return Collections.unmodifiableList(pokemongs);
}
public void setPokemongs(List<TrainerPokemong> pokemongs) {
this.pokemongs = pokemongs;
}
}

@ -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;
}
}

@ -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<TypeName> weakAgainst;
private List<TypeName> effectiveAgainst;
public Type() {}
public TypeName getName() {
return name;
}
public void setName(TypeName name) {
this.name = name;
}
public List<TypeName> getWeakAgainst() {
return weakAgainst;
}
public void setWeakAgainst(List<TypeName> weakAgainst) {
this.weakAgainst = weakAgainst;
}
public List<TypeName> getEffectiveAgainst() {
return effectiveAgainst;
}
public void setEffectiveAgainst(List<TypeName> 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<TypeName> weakAgainst;
private List<TypeName> effectiveAgainst;
public Type() {}
public TypeName getName() {
return name;
}
public void setName(TypeName name) {
this.name = name;
}
public List<TypeName> getWeakAgainst() {
return Collections.unmodifiableList(weakAgainst);
}
public void setWeakAgainst(List<TypeName> weakAgainst) {
this.weakAgainst = weakAgainst;
}
public List<TypeName> getEffectiveAgainst() {
return Collections.unmodifiableList(effectiveAgainst);
}
public void setEffectiveAgainst(List<TypeName> 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);
}
}

@ -0,0 +1,4 @@
@NonNullApi
package fr.uca.iut;
import com.mongodb.lang.NonNullApi;

@ -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<T extends GenericEntity> {
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<T> getCollection();
public void persist(@NotNull T entity) {
getCollection().insertOne(entity);
}
public List<T> 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;
}
}

@ -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<Move> {
// 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<Move> getCollection() {
MongoDatabase db = mongoClient.getDatabase(DB_NAME);
return db.getCollection(Move.COLLECTION_NAME, Move.class);
}
}

@ -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<Pokemong> 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<Pokemong> 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<Pokemong> {
// 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<Pokemong> findByMove(String moveId) {
Bson filter = Filters.elemMatch("moveSet", Filters.eq("_id", new ObjectId(moveId)));
return getCollection().find(filter)
.into(new ArrayList<>());
}
@Override
protected MongoCollection<Pokemong> getCollection() {
MongoDatabase db = mongoClient.getDatabase(DB_NAME);
return db.getCollection(Pokemong.COLLECTION_NAME, Pokemong.class);
}
}

@ -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<Trainer> {
// 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<Trainer> getCollection() {
MongoDatabase db = mongoClient.getDatabase(DB_NAME);
return db.getCollection(Trainer.COLLECTION_NAME, Trainer.class);
}
}

@ -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<T extends GenericEntity> {
protected GenericRepository<T> repository;
public void setRepository(GenericRepository<T> 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<T> 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");
}
}
}

@ -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<Move> {
@Inject
MoveRepository moveRepository;
@Inject
PokemongService pokemongService;
@PostConstruct
public void init() {
setRepository(moveRepository);
}
@Override
public void deleteOneById(String id) {
super.deleteOneById(id);
List<Pokemong> 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<Pokemong> 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<String> 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);
}
}

@ -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<Pokemong> 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<Pokemong> {
@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<String> 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<Type> 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<PokemongMove> 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<Pokemong> 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<TrainerPokemong> trainerPokemongs, @Nullable String trainerId) {
for (TrainerPokemong trainerPokemong : trainerPokemongs) {
Pokemong pokemong = getOneById(trainerPokemong.getId());
if (pokemong != null) {
pokemong.setTrainer(trainerId);
updateOne(pokemong);
}
}
}
}

@ -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<Trainer> {
@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<String> 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<String> 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<TrainerPokemong> 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));
}
}
}

@ -0,0 +1,7 @@
package fr.uca.iut.utils;
public class StringUtils {
public static boolean isBlankStringOrNull(String string) {
return string == null || string.isBlank();
}
}

@ -0,0 +1,7 @@
package fr.uca.iut.utils.enums;
public enum MoveCategoryName {
PHYSICAL,
SPECIAL,
STATUS
}

@ -1,4 +1,4 @@
package fr.uca.iut.utils;
package fr.uca.iut.utils.enums;
public enum PokemongName {
BULBASAUR,

@ -1,4 +1,4 @@
package fr.uca.iut.utils;
package fr.uca.iut.utils.enums;
public enum TypeName {
NORMAL,

@ -0,0 +1,7 @@
package fr.uca.iut.utils.exceptions;
public class NonValidEntityException extends RuntimeException {
public NonValidEntityException(String message) {
super(message);
}
}

File diff suppressed because it is too large Load Diff

@ -1,2 +1,3 @@
quarkus.mongodb.connection-string=mongodb+srv://<username>:<password>@<cluster>.<node>.mongodb.net
quarkus.mongodb.database=<database>
quarkus.mongodb.connection-string=mongodb+srv://<username>:<password>@<cluster>.<node>.mongodb.net
quarkus.mongodb.database=<database>
quarkus.smallrye-openapi.path=META-INF/openapi.yaml

Loading…
Cancel
Save