diff --git a/src/app/create_database.py b/src/app/create_database.py
index 06b44ae..aa7eb85 100644
--- a/src/app/create_database.py
+++ b/src/app/create_database.py
@@ -5,15 +5,15 @@
from sqlalchemy.exc import IntegrityError
from src.app.database import create_db_and_tables, engine
-from src.app.models import (
- Item,
+from src.app.models.dqm1.item import Item
+from src.app.models.dqm1.monster import (
MonsterBreedingLink,
MonsterDetail,
MonsterFamily,
MonsterSkillLink,
- Skill,
- SkillCombine,
)
+from src.app.models.dqm1.skill import Skill, SkillCombine
+
current_dir = Path(__file__).resolve().parent
csv_dir = current_dir.parent / "csv_files"
diff --git a/src/app/database.py b/src/app/database.py
index 47a1d14..a79540e 100644
--- a/src/app/database.py
+++ b/src/app/database.py
@@ -1,6 +1,6 @@
from pathlib import Path
-from sqlmodel import SQLModel, create_engine
+from sqlmodel import Session, SQLModel, create_engine
app_dir = Path(__file__).resolve().parent
project_dir = app_dir.parent
@@ -13,3 +13,8 @@
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
+
+
+async def get_session():
+ with Session(engine) as session:
+ yield session
diff --git a/src/app/main.py b/src/app/main.py
index d9190bf..0c3eabf 100644
--- a/src/app/main.py
+++ b/src/app/main.py
@@ -1,31 +1,8 @@
-from typing import List, Optional
-
-from fastapi import Depends, FastAPI, HTTPException
+from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
-from sqlmodel import Session, select
-from src.app.database import engine
-from src.app.model_enums import (
- ItemCategory,
- ItemSellLocation,
- SkillCategory,
- SkillFamily,
-)
-from src.app.models import (
- Item,
- MonsterBreedingLink,
- MonsterBreedingLinkReadWithInfo,
- MonsterDetail,
- MonsterDetailSkill,
- MonsterDetailWithFamily,
- MonsterFamily,
- MonsterFamilyReadWithMonsterDetail,
- Skill,
- SkillCombine,
- SkillCombineRead,
- SkillUpgradeRead,
-)
+from src.app.routers import dqm1_endpoints
tags_metadata = [
{
@@ -66,10 +43,7 @@
allow_headers=["*"],
)
-
-async def get_session(): # place in database.py?
- with Session(engine) as session:
- yield session
+app.include_router(dqm1_endpoints.router)
@app.get("/")
@@ -77,145 +51,3 @@ def root():
return {
"message": ("Welcome to the DQMonsters API. " "Go to the Swagger UI interface")
}
-
-
-@app.get(
- "/dqm1/monsters",
- response_model=List[MonsterDetailWithFamily],
- tags=["dqm1 monsters"],
-)
-async def read_monsters(
- *, session: Session = Depends(get_session), family: Optional[int] = None
-):
- """
- **Parameter Descriptions**
- **new_name** : updated name used in later Dragon Quest games
- **old_name** : name used in the game
- **description** : in game beastiary description
- **family** : a monster is part of one of 10 different monster families
- """
- monsters = select(MonsterDetail)
- if family:
- monsters = monsters.where(MonsterDetail.family_id == family)
- monsters_result = session.exec(monsters).all()
- return monsters_result
-
-
-@app.get(
- "/dqm1/monsters/{monster_id}",
- response_model=MonsterDetailWithFamily,
- tags=["dqm1 monsters"],
-)
-async def read_monster(*, session: Session = Depends(get_session), monster_id: int):
- monster = session.get(MonsterDetail, monster_id)
- if not monster:
- raise HTTPException(status_code=404, detail="Monster not found")
- return monster
-
-
-@app.get(
- "/dqm1/monstersandskill/{monster_id}",
- response_model=MonsterDetailSkill,
- tags=["dqm1 monsters"],
-)
-async def read_monster_skill(
- *, session: Session = Depends(get_session), monster_id: int
-):
- monster = session.get(MonsterDetail, monster_id)
- if not monster:
- raise HTTPException(status_code=404, detail="Monster not found")
- return monster
-
-
-@app.get(
- "/dqm1/family/{family_id}",
- response_model=MonsterFamilyReadWithMonsterDetail,
- tags=["dqm1 monsters"],
-)
-async def read_family(*, session: Session = Depends(get_session), family_id: int):
- family = session.get(MonsterFamily, family_id)
- if not family:
- raise HTTPException(status_code=404, detail="Family not found")
- return family
-
-
-@app.get("/dqm1/skills", tags=["dqm1 skills"])
-async def read_skills(
- *,
- session: Session = Depends(get_session),
- category: Optional[SkillCategory] = None,
- skill_family: Optional[SkillFamily] = None,
-):
- skills = select(Skill)
- if category:
- skills = skills.where(Skill.category_type == category)
- if skill_family:
- skills = skills.where(Skill.family_type == skill_family)
- skills_result = session.exec(skills).all()
- return skills_result
-
-
-@app.get(
- "/dqm1/skills/{skill_id}", response_model=SkillUpgradeRead, tags=["dqm1 skills"]
-)
-async def read_skill(*, session: Session = Depends(get_session), skill_id: int):
- skill = session.get(Skill, skill_id)
- if not skill:
- raise HTTPException(status_code=404, detail="Skill not found")
- return skill
-
-
-@app.get(
- "/dqm1/skillcombine/{skill_id}",
- response_model=List[SkillCombineRead],
- tags=["dqm1 skills"],
-)
-async def get_skill_combo(*, session: Session = Depends(get_session), skill_id: int):
- query = select(SkillCombine).where(SkillCombine.combo_skill_id == skill_id)
- skill = session.exec(query).all()
- return skill
-
-
-@app.get("/dqm1/items", tags=["dqm1 items"])
-async def read_items(
- *,
- session: Session = Depends(get_session),
- category: Optional[ItemCategory] = None,
- selllocation: Optional[ItemSellLocation] = None,
-):
- items = select(Item)
- if category:
- items = items.where(Item.item_category == category)
- if selllocation:
- items = items.where(Item.sell_location == selllocation)
- items_result = session.exec(items).all()
- return items_result
-
-
-@app.get("/dqm1/items/{item_id}", tags=["dqm1 items"])
-async def read_item(*, session: Session = Depends(get_session), item_id: int):
- item = session.get(Item, item_id)
- if not item:
- raise HTTPException(status_code=404, detail="Item not found")
- return item
-
-
-@app.get(
- "/dqm1/breeding/{monster_id}",
- response_model=List[MonsterBreedingLinkReadWithInfo],
- tags=["dqm1 monsters"],
-)
-async def get_breeding_combos(
- *, session: Session = Depends(get_session), monster_id: int
-):
- """
- Given a monster_id, finds all breeding combination that results in
- the target monster or uses the target monster as a parent
- """
- query = select(MonsterBreedingLink).where(
- (MonsterBreedingLink.child_id == monster_id)
- | (MonsterBreedingLink.pedigree_id == monster_id)
- | (MonsterBreedingLink.parent2_id == monster_id)
- )
- breeding_combos = session.exec(query).all()
- return breeding_combos
diff --git a/src/app/models/__init__.py b/src/app/models/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/models/dqm1/__init__.py b/src/app/models/dqm1/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/model_enums.py b/src/app/models/dqm1/enums.py
similarity index 100%
rename from src/app/model_enums.py
rename to src/app/models/dqm1/enums.py
diff --git a/src/app/models/dqm1/item.py b/src/app/models/dqm1/item.py
new file mode 100644
index 0000000..708732c
--- /dev/null
+++ b/src/app/models/dqm1/item.py
@@ -0,0 +1,17 @@
+from typing import Optional
+
+from sqlmodel import Field, SQLModel
+
+
+class Item(SQLModel, table=True):
+ """
+ Lists all items sold in shops and found in the field
+ """
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ item_name: str
+ item_category: str
+ item_description: str
+ price: Optional[int] = Field(default=None)
+ sell_price: Optional[int] = Field(default=None)
+ sell_location: str
diff --git a/src/app/models.py b/src/app/models/dqm1/monster.py
similarity index 58%
rename from src/app/models.py
rename to src/app/models/dqm1/monster.py
index d372bdc..1666b7a 100644
--- a/src/app/models.py
+++ b/src/app/models/dqm1/monster.py
@@ -1,264 +1,150 @@
-from typing import List, Optional
-
-from sqlmodel import Field, Relationship, SQLModel
-
-
-class MonsterSkillLink(SQLModel, table=True):
- """
- many-to-many association table linking a monster to three different skills.
- """
-
- id: Optional[int] = Field(default=None, primary_key=True)
- monster_id: Optional[int] = Field(
- default=None,
- foreign_key="monsterdetail.id",
- )
- skill_id: Optional[int] = Field(
- default=None,
- foreign_key="skill.id",
- )
-
-
-class MonsterDetailBase(SQLModel):
- """
- Monster details from in-game bestiary. Shows name, family, and description.
- """
-
- new_name: str
- old_name: str
- description: str
-
- # one-to-many relation where a family is linked to many monsters
- family_id: int = Field(foreign_key="monsterfamily.id")
-
-
-class MonsterDetail(MonsterDetailBase, table=True):
- id: Optional[int] = Field(default=None, primary_key=True)
-
- family: List["MonsterFamily"] = Relationship(back_populates="monsters")
- skills: List["Skill"] = Relationship(
- back_populates="monsters", link_model=MonsterSkillLink
- )
-
-
-class MonsterDetailRead(MonsterDetailBase):
- id: int
-
-
-class MonsterFamilyBase(SQLModel):
- """
- There are 10 monster families in the game.
- """
-
- family_eng: str
-
-
-class MonsterFamily(MonsterFamilyBase, table=True):
- """
- one-to-many relation between family and monsters.
- """
-
- id: Optional[int] = Field(default=None, primary_key=True)
- monsters: List[MonsterDetail] = Relationship(back_populates="family")
-
-
-class MonsterFamilyRead(MonsterFamilyBase):
- id: int
-
-
-class MonsterDetailWithFamily(MonsterDetailRead):
- family: Optional[MonsterFamilyRead]
-
-
-class MonsterFamilyReadWithMonsterDetail(MonsterFamilyRead):
- monsters: List[MonsterDetailRead] = []
-
-
-class MonsterBreedingLinkBase(SQLModel):
- child_id: Optional[int] = Field(default=None, foreign_key="monsterdetail.id")
- pedigree_id: Optional[int] = Field(default=None, foreign_key="monsterdetail.id")
- parent2_id: Optional[int] = Field(default=None, foreign_key="monsterdetail.id")
- pedigree_family_id: Optional[int] = Field(
- default=None, foreign_key="monsterfamily.id"
- )
- family2_id: Optional[int] = Field(default=None, foreign_key="monsterfamily.id")
-
-
-class MonsterBreedingLink(MonsterBreedingLinkBase, table=True):
- """
- many-to-many association table between MonsterDetail and MonsterFamily
- that represents breeding combinations.
-
- child_id, pedigree, and parent_2 represent individual monster ids.
- pedigree_family and family_2 represent family type.
-
- In order to make new monster, two parents are required.
-
- 4 different combinations possible:
- pedigree + parent_2 -- specific monster + specific monster
- pedigree + family_2 -- specific monster + any monster from the family type
- pedigree_family + parent_2 -- specific family type + specific monster
- pedigree_family + family_2 -- family + different family type
- """
-
- id: Optional[int] = Field(default=None, primary_key=True)
- child: "MonsterDetail" = Relationship(
- sa_relationship_kwargs={
- "primaryjoin": "MonsterBreedingLink.child_id==MonsterDetail.id",
- "lazy": "joined",
- }
- )
- pedigree: "MonsterDetail" = Relationship(
- sa_relationship_kwargs={
- "primaryjoin": "MonsterBreedingLink.pedigree_id==MonsterDetail.id",
- "lazy": "joined",
- }
- )
- parent2: "MonsterDetail" = Relationship(
- sa_relationship_kwargs={
- "primaryjoin": "MonsterBreedingLink.parent2_id==MonsterDetail.id",
- "lazy": "joined",
- }
- )
- pedigree_family: "MonsterFamily" = Relationship(
- sa_relationship_kwargs={
- "primaryjoin": "MonsterBreedingLink.pedigree_family_id"
- "==MonsterFamily.id",
- "lazy": "joined",
- }
- )
- family2: "MonsterFamily" = Relationship(
- sa_relationship_kwargs={
- "primaryjoin": "MonsterBreedingLink.family2_id==MonsterFamily.id",
- "lazy": "joined",
- }
- )
-
-
-class MonsterBreedingLinkRead(MonsterBreedingLinkBase):
- id: int
-
-
-class MonsterBreedingLinkReadWithInfo(MonsterBreedingLinkRead):
- child: Optional[MonsterDetailRead]
- pedigree: Optional[MonsterDetailRead]
- parent2: Optional[MonsterDetailRead]
- pedigree_family: Optional[MonsterFamilyRead]
- family2: Optional[MonsterFamilyRead]
-
-
-class SkillBase(SQLModel):
- """
- Shows description, MP cost, and required stats to learn skill.
- Each monster naturally learns 3 skills.
- """
-
- category_type: str
- family_type: str
- new_name: Optional[str] = Field(default=None)
- old_name: str
- description: str
- mp_cost: int
- required_level: int
- required_hp: Optional[int] = None
- required_mp: Optional[int] = None
- required_attack: Optional[int] = None
- required_defense: Optional[int] = None
- required_speed: Optional[int] = None
- required_intelligence: Optional[int] = None
-
-
-class Skill(SkillBase, table=True):
- id: Optional[int] = Field(default=None, primary_key=True)
-
- upgrade_to_id: Optional[int] = Field(
- foreign_key="skill.id", # lowercase refers to database table name
- default=None,
- )
- upgrade_to: Optional["Skill"] = Relationship(
- sa_relationship_kwargs={
- "primaryjoin": "Skill.upgrade_to_id==Skill.id",
- "lazy": "joined",
- "remote_side": "Skill.id", # uppercase refers to this Skill class
- }
- )
-
- upgrade_from_id: Optional[int] = Field(
- foreign_key="skill.id",
- default=None,
- )
- upgrade_from: Optional["Skill"] = Relationship(
- sa_relationship_kwargs={
- "primaryjoin": "Skill.upgrade_from_id==Skill.id",
- "lazy": "joined",
- "remote_side": "Skill.id",
- }
- )
-
- monsters: List[MonsterDetail] = Relationship(
- back_populates="skills", link_model=MonsterSkillLink
- )
-
-
-class SkillRead(SkillBase):
- id: int
-
-
-class SkillReadWithMonster(SkillRead):
- monsters: Optional[MonsterDetailRead]
-
-
-class SkillUpgradeRead(SkillRead):
- upgrade_to: Optional[Skill]
- upgrade_from: Optional[Skill]
-
-
-class MonsterDetailSkill(MonsterDetailWithFamily):
- skills: List[SkillRead] = []
-
-
-class SkillCombineBase(SQLModel):
- combo_skill_id: Optional[int] = Field(default=None, foreign_key="skill.id")
- needed_skill_id: Optional[int] = Field(default=None, foreign_key="skill.id")
-
-
-class SkillCombine(SkillCombineBase, table=True):
- """
- many-to-many association table showing certain needed skills combine to
- learn new combo skill.
- """
-
- id: Optional[int] = Field(default=None, primary_key=True)
-
- combo_skill: Skill = Relationship(
- sa_relationship_kwargs={
- "primaryjoin": "SkillCombine.combo_skill_id==Skill.id",
- "lazy": "joined",
- }
- )
-
- needed_skill: Skill = Relationship(
- sa_relationship_kwargs={
- "primaryjoin": "SkillCombine.needed_skill_id==Skill.id",
- "lazy": "joined",
- }
- )
-
-
-class SkillCombineRead(SkillCombineBase):
- id: int
- needed_skill: Optional[SkillRead]
-
-
-class Item(SQLModel, table=True):
- """
- Lists all items sold in shops and found in the field
- """
-
- id: Optional[int] = Field(default=None, primary_key=True)
- item_name: str
- item_category: str
- item_description: str
- price: Optional[int] = Field(default=None)
- sell_price: Optional[int] = Field(default=None)
- sell_location: str
+from typing import List, Optional, TYPE_CHECKING
+
+from sqlmodel import Field, Relationship, SQLModel
+
+if TYPE_CHECKING:
+ from .skill import Skill
+
+
+class MonsterSkillLink(SQLModel, table=True):
+ """
+ many-to-many association table linking a monster to three different skills.
+ """
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ monster_id: Optional[int] = Field(
+ default=None,
+ foreign_key="monsterdetail.id",
+ )
+ skill_id: Optional[int] = Field(
+ default=None,
+ foreign_key="skill.id",
+ )
+
+
+class MonsterDetailBase(SQLModel):
+ """
+ Monster details from in-game bestiary. Shows name, family, and description.
+ """
+
+ new_name: str
+ old_name: str
+ description: str
+
+ # one-to-many relation where a family is linked to many monsters
+ family_id: int = Field(foreign_key="monsterfamily.id")
+
+
+class MonsterDetail(MonsterDetailBase, table=True):
+ id: Optional[int] = Field(default=None, primary_key=True)
+
+ family: List["MonsterFamily"] = Relationship(back_populates="monsters")
+ skills: List["Skill"] = Relationship(
+ back_populates="monsters", link_model=MonsterSkillLink
+ )
+
+
+class MonsterDetailRead(MonsterDetailBase):
+ id: int
+
+
+class MonsterFamilyBase(SQLModel):
+ """
+ There are 10 monster families in the game.
+ """
+
+ family_eng: str
+
+
+class MonsterFamily(MonsterFamilyBase, table=True):
+ """
+ one-to-many relation between family and monsters.
+ """
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ monsters: List[MonsterDetail] = Relationship(back_populates="family")
+
+
+class MonsterFamilyRead(MonsterFamilyBase):
+ id: int
+
+
+class MonsterDetailWithFamily(MonsterDetailRead):
+ family: Optional[MonsterFamilyRead]
+
+
+class MonsterFamilyReadWithMonsterDetail(MonsterFamilyRead):
+ monsters: List[MonsterDetailRead] = []
+
+
+class MonsterBreedingLinkBase(SQLModel):
+ child_id: Optional[int] = Field(default=None, foreign_key="monsterdetail.id")
+ pedigree_id: Optional[int] = Field(default=None, foreign_key="monsterdetail.id")
+ parent2_id: Optional[int] = Field(default=None, foreign_key="monsterdetail.id")
+ pedigree_family_id: Optional[int] = Field(
+ default=None, foreign_key="monsterfamily.id"
+ )
+ family2_id: Optional[int] = Field(default=None, foreign_key="monsterfamily.id")
+
+
+class MonsterBreedingLink(MonsterBreedingLinkBase, table=True):
+ """
+ many-to-many association table between MonsterDetail and MonsterFamily
+ that represents breeding combinations.
+
+ child_id, pedigree, and parent_2 represent individual monster ids.
+ pedigree_family and family_2 represent family type.
+
+ In order to make new monster, two parents are required.
+
+ 4 different combinations possible:
+ pedigree + parent_2 -- specific monster + specific monster
+ pedigree + family_2 -- specific monster + any monster from the family type
+ pedigree_family + parent_2 -- specific family type + specific monster
+ pedigree_family + family_2 -- family + different family type
+ """
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ child: "MonsterDetail" = Relationship(
+ sa_relationship_kwargs={
+ "primaryjoin": "MonsterBreedingLink.child_id==MonsterDetail.id",
+ "lazy": "joined",
+ }
+ )
+ pedigree: "MonsterDetail" = Relationship(
+ sa_relationship_kwargs={
+ "primaryjoin": "MonsterBreedingLink.pedigree_id==MonsterDetail.id",
+ "lazy": "joined",
+ }
+ )
+ parent2: "MonsterDetail" = Relationship(
+ sa_relationship_kwargs={
+ "primaryjoin": "MonsterBreedingLink.parent2_id==MonsterDetail.id",
+ "lazy": "joined",
+ }
+ )
+ pedigree_family: "MonsterFamily" = Relationship(
+ sa_relationship_kwargs={
+ "primaryjoin": "MonsterBreedingLink.pedigree_family_id"
+ "==MonsterFamily.id",
+ "lazy": "joined",
+ }
+ )
+ family2: "MonsterFamily" = Relationship(
+ sa_relationship_kwargs={
+ "primaryjoin": "MonsterBreedingLink.family2_id==MonsterFamily.id",
+ "lazy": "joined",
+ }
+ )
+
+
+class MonsterBreedingLinkRead(MonsterBreedingLinkBase):
+ id: int
+
+
+class MonsterBreedingLinkReadWithInfo(MonsterBreedingLinkRead):
+ child: Optional[MonsterDetailRead]
+ pedigree: Optional[MonsterDetailRead]
+ parent2: Optional[MonsterDetailRead]
+ pedigree_family: Optional[MonsterFamilyRead]
+ family2: Optional[MonsterFamilyRead]
diff --git a/src/app/models/dqm1/skill.py b/src/app/models/dqm1/skill.py
new file mode 100644
index 0000000..0d92333
--- /dev/null
+++ b/src/app/models/dqm1/skill.py
@@ -0,0 +1,120 @@
+from typing import List, Optional, TYPE_CHECKING
+
+from sqlmodel import Field, Relationship, SQLModel
+from .monster import (
+ MonsterDetail,
+ MonsterSkillLink,
+ MonsterDetailRead,
+ MonsterDetailWithFamily,
+)
+
+if TYPE_CHECKING:
+ from .monster import (
+ MonsterDetail,
+ MonsterSkillLink,
+ MonsterDetailRead,
+ MonsterDetailWithFamily,
+ )
+
+
+class SkillBase(SQLModel):
+ """
+ Shows description, MP cost, and required stats to learn skill.
+ Each monster naturally learns 3 skills.
+ """
+
+ category_type: str
+ family_type: str
+ new_name: Optional[str] = Field(default=None)
+ old_name: str
+ description: str
+ mp_cost: int
+ required_level: int
+ required_hp: Optional[int] = None
+ required_mp: Optional[int] = None
+ required_attack: Optional[int] = None
+ required_defense: Optional[int] = None
+ required_speed: Optional[int] = None
+ required_intelligence: Optional[int] = None
+
+
+class Skill(SkillBase, table=True):
+ id: Optional[int] = Field(default=None, primary_key=True)
+
+ upgrade_to_id: Optional[int] = Field(
+ foreign_key="skill.id", # lowercase refers to database table name
+ default=None,
+ )
+ upgrade_to: Optional["Skill"] = Relationship(
+ sa_relationship_kwargs={
+ "primaryjoin": "Skill.upgrade_to_id==Skill.id",
+ "lazy": "joined",
+ "remote_side": "Skill.id", # uppercase refers to this Skill class
+ }
+ )
+
+ upgrade_from_id: Optional[int] = Field(
+ foreign_key="skill.id",
+ default=None,
+ )
+ upgrade_from: Optional["Skill"] = Relationship(
+ sa_relationship_kwargs={
+ "primaryjoin": "Skill.upgrade_from_id==Skill.id",
+ "lazy": "joined",
+ "remote_side": "Skill.id",
+ }
+ )
+
+ monsters: List["MonsterDetail"] = Relationship(
+ back_populates="skills", link_model=MonsterSkillLink
+ )
+
+
+class SkillRead(SkillBase):
+ id: int
+
+
+class SkillReadWithMonster(SkillRead):
+ monsters: Optional[MonsterDetailRead]
+
+
+class SkillUpgradeRead(SkillRead):
+ upgrade_to: Optional[Skill]
+ upgrade_from: Optional[Skill]
+
+
+class MonsterDetailSkill(MonsterDetailWithFamily):
+ skills: List[SkillRead] = []
+
+
+class SkillCombineBase(SQLModel):
+ combo_skill_id: Optional[int] = Field(default=None, foreign_key="skill.id")
+ needed_skill_id: Optional[int] = Field(default=None, foreign_key="skill.id")
+
+
+class SkillCombine(SkillCombineBase, table=True):
+ """
+ many-to-many association table showing certain needed skills combine to
+ learn new combo skill.
+ """
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+
+ combo_skill: Skill = Relationship(
+ sa_relationship_kwargs={
+ "primaryjoin": "SkillCombine.combo_skill_id==Skill.id",
+ "lazy": "joined",
+ }
+ )
+
+ needed_skill: Skill = Relationship(
+ sa_relationship_kwargs={
+ "primaryjoin": "SkillCombine.needed_skill_id==Skill.id",
+ "lazy": "joined",
+ }
+ )
+
+
+class SkillCombineRead(SkillCombineBase):
+ id: int
+ needed_skill: Optional[SkillRead]
diff --git a/src/app/routers/__init__.py b/src/app/routers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/routers/dqm1_endpoints.py b/src/app/routers/dqm1_endpoints.py
new file mode 100644
index 0000000..70fa1df
--- /dev/null
+++ b/src/app/routers/dqm1_endpoints.py
@@ -0,0 +1,172 @@
+from typing import List, Optional
+
+from fastapi import APIRouter, Depends, HTTPException
+from sqlmodel import Session, select
+
+from src.app.database import get_session
+from src.app.models.dqm1.enums import (
+ ItemCategory,
+ ItemSellLocation,
+ SkillCategory,
+ SkillFamily,
+)
+from src.app.models.dqm1.item import Item
+from src.app.models.dqm1.monster import (
+ MonsterBreedingLink,
+ MonsterBreedingLinkReadWithInfo,
+ MonsterDetail,
+ MonsterDetailWithFamily,
+ MonsterFamily,
+ MonsterFamilyReadWithMonsterDetail,
+)
+from src.app.models.dqm1.skill import (
+ MonsterDetailSkill,
+ Skill,
+ SkillCombine,
+ SkillCombineRead,
+ SkillUpgradeRead,
+)
+
+router = APIRouter(
+ prefix="/dqm1",
+)
+
+
+@router.get(
+ "/monsters",
+ response_model=List[MonsterDetailWithFamily],
+ tags=["dqm1 monsters"],
+)
+async def read_monsters(
+ *, session: Session = Depends(get_session), family: Optional[int] = None
+):
+ """
+ **Parameter Descriptions**
+ **new_name** : updated name used in later Dragon Quest games
+ **old_name** : name used in the game
+ **description** : in game beastiary description
+ **family** : a monster is part of one of 10 different monster families
+ """
+ monsters = select(MonsterDetail)
+ if family:
+ monsters = monsters.where(MonsterDetail.family_id == family)
+ monsters_result = session.exec(monsters).all()
+ return monsters_result
+
+
+@router.get(
+ "/monsters/{monster_id}",
+ response_model=MonsterDetailWithFamily,
+ tags=["dqm1 monsters"],
+)
+async def read_monster(*, session: Session = Depends(get_session), monster_id: int):
+ monster = session.get(MonsterDetail, monster_id)
+ if not monster:
+ raise HTTPException(status_code=404, detail="Monster not found")
+ return monster
+
+
+@router.get(
+ "/monstersandskill/{monster_id}",
+ response_model=MonsterDetailSkill,
+ tags=["dqm1 monsters"],
+)
+async def read_monster_skill(
+ *, session: Session = Depends(get_session), monster_id: int
+):
+ monster = session.get(MonsterDetail, monster_id)
+ if not monster:
+ raise HTTPException(status_code=404, detail="Monster not found")
+ return monster
+
+
+@router.get(
+ "/family/{family_id}",
+ response_model=MonsterFamilyReadWithMonsterDetail,
+ tags=["dqm1 monsters"],
+)
+async def read_family(*, session: Session = Depends(get_session), family_id: int):
+ family = session.get(MonsterFamily, family_id)
+ if not family:
+ raise HTTPException(status_code=404, detail="Family not found")
+ return family
+
+
+@router.get("/skills", tags=["dqm1 skills"])
+async def read_skills(
+ *,
+ session: Session = Depends(get_session),
+ category: Optional[SkillCategory] = None,
+ skill_family: Optional[SkillFamily] = None,
+):
+ skills = select(Skill)
+ if category:
+ skills = skills.where(Skill.category_type == category)
+ if skill_family:
+ skills = skills.where(Skill.family_type == skill_family)
+ skills_result = session.exec(skills).all()
+ return skills_result
+
+
+@router.get("/skills/{skill_id}", response_model=SkillUpgradeRead, tags=["dqm1 skills"])
+async def read_skill(*, session: Session = Depends(get_session), skill_id: int):
+ skill = session.get(Skill, skill_id)
+ if not skill:
+ raise HTTPException(status_code=404, detail="Skill not found")
+ return skill
+
+
+@router.get(
+ "/skillcombine/{skill_id}",
+ response_model=List[SkillCombineRead],
+ tags=["dqm1 skills"],
+)
+async def get_skill_combo(*, session: Session = Depends(get_session), skill_id: int):
+ query = select(SkillCombine).where(SkillCombine.combo_skill_id == skill_id)
+ skill = session.exec(query).all()
+ return skill
+
+
+@router.get("/items", tags=["dqm1 items"])
+async def read_items(
+ *,
+ session: Session = Depends(get_session),
+ category: Optional[ItemCategory] = None,
+ selllocation: Optional[ItemSellLocation] = None,
+):
+ items = select(Item)
+ if category:
+ items = items.where(Item.item_category == category)
+ if selllocation:
+ items = items.where(Item.sell_location == selllocation)
+ items_result = session.exec(items).all()
+ return items_result
+
+
+@router.get("/items/{item_id}", tags=["dqm1 items"])
+async def read_item(*, session: Session = Depends(get_session), item_id: int):
+ item = session.get(Item, item_id)
+ if not item:
+ raise HTTPException(status_code=404, detail="Item not found")
+ return item
+
+
+@router.get(
+ "/breeding/{monster_id}",
+ response_model=List[MonsterBreedingLinkReadWithInfo],
+ tags=["dqm1 monsters"],
+)
+async def get_breeding_combos(
+ *, session: Session = Depends(get_session), monster_id: int
+):
+ """
+ Given a monster_id, finds all breeding combination that results in
+ the target monster or uses the target monster as a parent
+ """
+ query = select(MonsterBreedingLink).where(
+ (MonsterBreedingLink.child_id == monster_id)
+ | (MonsterBreedingLink.pedigree_id == monster_id)
+ | (MonsterBreedingLink.parent2_id == monster_id)
+ )
+ breeding_combos = session.exec(query).all()
+ return breeding_combos
diff --git a/tests/conftest.py b/tests/conftest.py
index 1c158be..8b0b659 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -6,16 +6,16 @@
from sqlmodel import Session, SQLModel, create_engine
from sqlmodel.pool import StaticPool
-from src.app.main import app, get_session
-from src.app.models import (
- Item,
+from src.app.database import get_session
+from src.app.main import app
+from src.app.models.dqm1.item import Item
+from src.app.models.dqm1.monster import (
MonsterBreedingLink,
MonsterDetail,
MonsterFamily,
MonsterSkillLink,
- Skill,
- SkillCombine,
)
+from src.app.models.dqm1.skill import Skill, SkillCombine
@pytest.fixture(name="session")
diff --git a/tests/test_insert_data.py b/tests/test_insert_data.py
index 5169a8c..db1ebb0 100644
--- a/tests/test_insert_data.py
+++ b/tests/test_insert_data.py
@@ -1,15 +1,14 @@
from fastapi.testclient import TestClient
from sqlmodel import Session
-from src.app.models import (
- Item,
+from src.app.models.dqm1.item import Item
+from src.app.models.dqm1.monster import (
MonsterBreedingLink,
MonsterDetail,
MonsterFamily,
MonsterSkillLink,
- Skill,
- SkillCombine,
)
+from src.app.models.dqm1.skill import Skill, SkillCombine
def test_read_root(client: TestClient):