Skip to content

XanderHendry/tavern_keeper_be_lite

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tavern Keeper Contributors Forks Issues


Logo

Tavern Keeper

This application is a Dungeons and Dragons Encounter Builder, with the intended user being a Dungeon Master. It allows the user to fill out a form to add encounter details including encounter name, party size, party level, summary, description, treasure & rewards, and monsters. Monsters from the D&D 5e compendium can be filtered and added based on size, name, hit points, and armor class attributes.

This GraphQL API handles the backend functionality including:

  • Consumption of third party D&D 5e GraphQL API
  • Query endpoint(s) for display of created encounters as well as monster details
  • Mutation endpoint(s) for creation of new encounters
  • Current deploy GraphQL API endpoint: `https://tavern-keeper-be.onrender.com/graphql`

Explore Backend docs »

Frontend Repository · Report Bug · Request Feature

Table of Contents

  1. About The Project
  2. Getting Started
  3. Database
  4. Endpoints
  5. Roadmap
  6. Contributors

About The Project

Tavern Keeper

Built With

Development

  • Ruby on Rails
  • GraphQL
  • PostgreSQL

Additional Tools and Deployment

  • Postman
  • CircleCI
  • Render

(back to top)

Gems

Production

  • graphql
  • graphlient
  • rack-cors
  • factory_bot_rails
  • faker

Testing and Development

  • rspec-rails
  • simplecov
  • factory_bot_rails
  • faker
  • shoulda-matchers
  • webmock
  • vcr
  • pry
  • debug

(back to top)

Getting Started

To get a local copy up and running follow these simple example steps.

Database

Installation

  1. Clone the repo
    git clone https://github.com/Tavern-Keeper-2308/tavern_keeper_be.git
  2. Gem Bundle
     bundle
  3. Rake
     rails db:{drop,create,migrate,seed}
  4. To run on local server, http://localhost:5000/
     rails s
  5. Live deploy via desired method

(back to top)

Testing

  1. Run test suite
    bundle exec rspec

(back to top)

Endpoints

  1. Query endpoints are documented showing full scope of available attributes.
  2. Attributes should all be written in queries and mutations using camelCase.
  3. Some attributes are nested under another, using curly brackets {}.
attribute {
  nestedAttribute
  anotherNestedAttribute
}
  1. Query and Mutation requests can be modified to include only desired attribute data in the response.
  2. Query and Mutation request attributes can be ordered designate the order of attributes in the response.
  3. Variables for mutations can be input in any order.
  • Gets list of all available monsters, with simple base attributes (Example only shows a few monsters, not full respsonse).
  • This endpoint is utilized by the frontend for encounter creation, displaying all possible monster choices from the D&D 5e compendium.
GraphQL Query
query getMonsters {
  monsters {
    monsterIndex
    monsterName
    size
    hitPoints
    armorClass
    type
    alignment
    challengeRating
  }
}
Response
{
    "data": {
        "monsters": [
            {
                "monsterIndex": "acolyte",
                "monsterName": "Acolyte",
                "size": "MEDIUM",
                "hitPoints": 9,
                "armorClass": 10,
                "type": "HUMANOID",
                "alignment": "any alignment",
                "challengeRating": "0.25"
            },
            {
                "monsterIndex": "aboleth",
                "monsterName": "Aboleth",
                "size": "LARGE",
                "hitPoints": 135,
                "armorClass": 17,
                "type": "ABERRATION",
                "alignment": "lawful evil",
                "challengeRating": "10"
            },
            {
                "monsterIndex": "adult-black-dragon",
                "monsterName": "Adult Black Dragon",
                "size": "HUGE",
                "hitPoints": 195,
                "armorClass": 19,
                "type": "DRAGON",
                "alignment": "chaotic evil",
                "challengeRating": "14"
            }
        ]
    }
}

(back to top)

  • Gets list of all monster details for a single monster by index.
  • Requires variable(s):
    index - String type.
  • This endpoint is utilized by the frontend for encounter details page, for monster details dropdowns.
GraphQL Query
query getMonster($index: String!) {
    monster(index: $index) {
        monsterIndex
        monsterName
        size
        type
        armorClass
        speed {
            walk
            fly
            swim
        }
        hitPoints
        strength
        dexterity
        constitution
        intelligence
        wisdom
        charisma
        damageVulnerabilities
        damageResistances
        damageImmunities
        conditionImmunities
        proficiencyBonus
        proficiencies {
            name
            value
        }
        senses {
            blindsight
            darkvision
            passivePerception
        }
        specialAbilities {
            name
            desc
        }
        actions {
            name
            desc
        }
        legendaryActions {
            name
            desc
        }
    }
}
GraphQL Variable(s)
{
    "index": "aboleth"
}
Response
{
    "data": {
        "monster": {
            "monsterIndex": "aboleth",
            "monsterName": "Aboleth",
            "size": "LARGE",
            "type": null,
            "armorClass": 17,
            "speed": {
                "walk": "10 ft.",
                "fly": null,
                "swim": "40 ft."
            },
            "hitPoints": 135,
            "strength": 21,
            "dexterity": 9,
            "constitution": 15,
            "intelligence": 18,
            "wisdom": 15,
            "charisma": 18,
            "damageVulnerabilities": [],
            "damageResistances": [],
            "damageImmunities": [],
            "conditionImmunities": [],
            "proficiencyBonus": 4,
            "proficiencies": [
                {
                    "name": "Saving Throw: CON",
                    "value": "6"
                },
                {
                    "name": "Saving Throw: INT",
                    "value": "8"
                },
                {
                    "name": "Saving Throw: WIS",
                    "value": "6"
                },
                {
                    "name": "Skill: History",
                    "value": "12"
                },
                {
                    "name": "Skill: Perception",
                    "value": "10"
                }
            ],
            "senses": {
                "blindsight": null,
                "darkvision": "120 ft.",
                "passivePerception": "20"
            },
            "specialAbilities": [
                {
                    "name": "Amphibious",
                    "desc": "The aboleth can breathe air and water."
                },
                {
                    "name": "Mucous Cloud",
                    "desc": "While underwater, the aboleth is surrounded by transformative mucus. A creature that touches the aboleth or that hits it with a melee attack while within 5 ft. of it must make a DC 14 Constitution saving throw. On a failure, the creature is diseased for 1d4 hours. The diseased creature can breathe only underwater."
                },
                {
                    "name": "Probing Telepathy",
                    "desc": "If a creature communicates telepathically with the aboleth, the aboleth learns the creature's greatest desires if the aboleth can see the creature."
                }
            ],
            "actions": [
                {
                    "name": "Multiattack",
                    "desc": "The aboleth makes three tentacle attacks."
                },
                {
                    "name": "Tentacle",
                    "desc": "Melee Weapon Attack: +9 to hit, reach 10 ft., one target. Hit: 12 (2d6 + 5) bludgeoning damage. If the target is a creature, it must succeed on a DC 14 Constitution saving throw or become diseased. The disease has no effect for 1 minute and can be removed by any magic that cures disease. After 1 minute, the diseased creature's skin becomes translucent and slimy, the creature can't regain hit points unless it is underwater, and the disease can be removed only by heal or another disease-curing spell of 6th level or higher. When the creature is outside a body of water, it takes 6 (1d12) acid damage every 10 minutes unless moisture is applied to the skin before 10 minutes have passed."
                },
                {
                    "name": "Tail",
                    "desc": "Melee Weapon Attack: +9 to hit, reach 10 ft., one target. Hit: 15 (3d6 + 5) bludgeoning damage."
                },
                {
                    "name": "Enslave",
                    "desc": "The aboleth targets one creature it can see within 30 ft. of it. The target must succeed on a DC 14 Wisdom saving throw or be magically charmed by the aboleth until the aboleth dies or until it is on a different plane of existence from the target. The charmed target is under the aboleth's control and can't take reactions, and the aboleth and the target can communicate telepathically with each other over any distance.\nWhenever the charmed target takes damage, the target can repeat the saving throw. On a success, the effect ends. No more than once every 24 hours, the target can also repeat the saving throw when it is at least 1 mile away from the aboleth."
                }
            ],
            "legendaryActions": [
                {
                    "name": "Detect",
                    "desc": "The aboleth makes a Wisdom (Perception) check."
                },
                {
                    "name": "Tail Swipe",
                    "desc": "The aboleth makes one tail attack."
                },
                {
                    "name": "Psychic Drain (Costs 2 Actions)",
                    "desc": "One creature charmed by the aboleth takes 10 (3d6) psychic damage, and the aboleth regains hit points equal to the damage the creature takes."
                }
            ]
        }
    }
}

(back to top)

  • Gets list of all encounters for a single user, by userName.
  • Requires variable(s):
    userName - String type
  • This endpoint is used by frontend to create an index page displaying all encounters created by a single user.
GraphQL Query
query getEncounters($userName: String!) {
    encounters(userName: $userName) {
        id
        userName
        encounterName
        partySize
        partyLevel
        summary
        description
        treasure
        encounterMonsters {
            monsterName
        }
    }
}
GraphQL Variable(s)
{
    "userName": "demo-many-encounters"
}
Response
{
    "data": {
        "encounters": [
            {
                "id": "2",
                "userName": "demo-many-encounters",
                "encounterName": "Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn",
                "partySize": 1,
                "partyLevel": 3,
                "summary": "Ere iron was found or tree was hewn",
                "description": "Gambrel fungus antiquarian gibbous gibbering unnamable. Furtive blasphemous cyclopean comprehension manuscript non-euclidean tentacles decadent. Antediluvian shunned mortal. Squamous non-euclidean cyclopean eldritch tenebrous gibbering charnel. Cyclopean stench furtive gibbering.",
                "treasure": "Amulet of Kynareth",
                "encounterMonsters": [
                    {
                        "monsterName": "Giant Shark"
                    },
                    {
                        "monsterName": "Aboleth"
                    },
                    {
                        "monsterName": "Aboleth"
                    }
                ]
            },
            {
                "id": "3",
                "userName": "demo-many-encounters",
                "encounterName": "Rawr R'lyeh wgah'nagl Ph'n ui mglw'nafh gl fhtagn",
                "partySize": 1,
                "partyLevel": 13,
                "summary": "Under the mountain dark and tall",
                "description": "Mortal madness furtive gibbering stygian. Mortal singular amorphous stygian stench antiquarian. Non-euclidean furtive decadent accursed comprehension cyclopean foetid fungus. Madness spectral stench charnel indescribable comprehension unutterable.",
                "treasure": "Gold and Ruby Circlet",
                "encounterMonsters": [
                    {
                        "monsterName": "Goblin"
                    },
                    {
                        "monsterName": "Goblin"
                    }
                ]
            },
            {
                "id": "4",
                "userName": "demo-many-encounters",
                "encounterName": "Tnafh Dargrlw'l fhtagne R'Ph'nglui mg",
                "partySize": 3,
                "partyLevel": 9,
                "summary": "The Fall of Gil-galad",
                "description": "Antiquarian stygian lurk charnel unnamable furtive. Non-euclidean blasphemous unmentionable dank stygian immemorial. Effulgence gibbous foetid antediluvian ululate non-euclidean gibbering squamous. Antediluvian daemoniac dank.",
                "treasure": "Nightweaver's Band",
                "encounterMonsters": [
                    {
                        "monsterName": "Aboleth"
                    },
                    {
                        "monsterName": "Aboleth"
                    }
                ]
            }
        ]
    }
}

(back to top)

  • Gets details for a single encounter, by encounter id
  • Requires variable(s):
    id - Integer type
  • This endpoint is utilized by the frontend for create a display page for a single encounter.
GraphQL Query
query getEncounter($id: ID!) {
    encounter(id: $id) {
        id
        userName
        encounterName
        partySize
        partyLevel
        summary
        description
        treasure
        encounterMonsters {
            monsterName
            monsterIndex
        }
    }
}
GraphQL Variable(s)
{
    "id": 2
}
Response
{
    "data": {
        "encounter": {
            "id": "2",
            "userName": "demo-many-encounters",
            "encounterName": "Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn",
            "partySize": 1,
            "partyLevel": 3,
            "summary": "Ere iron was found or tree was hewn",
            "description": "Gambrel fungus antiquarian gibbous gibbering unnamable. Furtive blasphemous cyclopean comprehension manuscript non-euclidean tentacles decadent. Antediluvian shunned mortal. Squamous non-euclidean cyclopean eldritch tenebrous gibbering charnel. Cyclopean stench furtive gibbering.",
            "treasure": "Amulet of Kynareth",
            "encounterMonsters": [
                {
                    "monsterName": "Giant Shark",
                    "monsterIndex": "giant-shark"
                },
                {
                    "monsterName": "Aboleth",
                    "monsterIndex": "aboleth"
                },
                {
                    "monsterName": "Aboleth",
                    "monsterIndex": "aboleth"
                }
            ]
        }
    }
}

(back to top)

  • Creates a new encounter based on input variables, userName, encounterName, partySize, partyLevel, summary, description, treasure, and encounterMonsterIndexes (this is an array of monster index strings).
  • Requires variable(s):
    userName - String type
    encounterName - String type
    partySize - Integer type
    partyLevel - Integer type
    summary - String type
    description - String type
    treasure - String type
    encounterMonsterIndexes - [String] type
  • This endpoint is utilized by the frontend to create a new encounter from user input on encounter builder page.
  • Response include encounter and errors, errors is an array and will be empty upon a successful mutation request and encounter creation.
  • If there are errors, only errors is returned in the response with message detailing the issue and location with where the error ocurred, and extensions containing details of the error.
GraphQL Mutation
mutation CreateEncounter($userName: String!, $encounterName: String!, $partySize: Int!, $partyLevel: Int!, $summary: String!, $description: String!, $treasure: String!, $encounterMonsterIndexes: [String!]!) {
    createEncounter(input: {
        userName: $userName,
        encounterName: $encounterName,
        partySize: $partySize,
        partyLevel: $partyLevel,
        summary: $summary,
        description: $description,
        treasure: $treasure,
        encounterMonsterIndexes: $encounterMonsterIndexes
    }) {
        encounter {
            userName
            id
            encounterName
            partySize
            partyLevel
            summary
            description
            treasure
            encounterMonsters {
                monsterIndex
                monsterName
            }
        }
        errors
    }
}    
GraphQL Variable(s)
{
  "userName": "WhoAmI",
  "encounterName": "Party Wipe",
  "partySize": 4,
  "partyLevel": 3,
  "summary": "I hope this works",
  "description": "Why does it have to be a string",
  "treasure": "We not deserve anything",
  "encounterMonsterIndexes": ["beholder", "goblin","goblin", "adult-black-dragon"]
}
Response
{
    "data": {
        "createEncounter": {
            "encounter": {
                "userName": "WhoAmI",
                "id": "13",
                "encounterName": "Party Wipe",
                "partySize": 4,
                "partyLevel": 3,
                "summary": "I hope this works",
                "description": "Why does it have to be a string",
                "treasure": "We not deserve anything",
                "encounterMonsters": [
                    {
                        "monsterIndex": "beholder",
                        "monsterName": "Beholder"
                    },
                    {
                        "monsterIndex": "goblin",
                        "monsterName": "Goblin"
                    },
                    {
                        "monsterIndex": "goblin",
                        "monsterName": "Goblin"
                    },
                    {
                        "monsterIndex": "adult-black-dragon",
                        "monsterName": "Adult Black Dragon"
                    }
                ]
            },
            "errors": []
        }
    }
}

(back to top)

Error Handling

GraphQL Mutation
mutation CreateEncounter($userName: String!, $encounterName: String!, $partySize: Int!, $partyLevel: Int!, $summary: String!, $description: String!, $treasure: String!, $encounterMonsterIndexes: [String!]!) {
    createEncounter(input: {
        userName: $userName,
        encounterName: $encounterName,
        partySize: $partySize,
        partyLevel: $partyLevel,
        summary: $summary,
        description: $description,
        treasure: $treasure,
        encounterMonsterIndexes: $encounterMonsterIndexes
    }) {
        encounter {
            userName
            id
            encounterName
            partySize
            partyLevel
            summary
            description
            treasure
            encounterMonsters {
                monsterIndex
                monsterName
            }
        }
        errors
    }
}    
GraphQL Variable(s)
{
  "encounterName": "Party Wipe",
  "partySize": 4,
  "partyLevel": 3,
  "summary": "I hope this works",
  "description": "Why does it have to be a string",
  "treasure": "We not deserve anything",
  "encounterMonsterIndexes": ["beholder", "goblin","goblin", "adult-black-dragon"]
}
Response
{
    "errors": [
        {
            "message": "Variable $userName of type String! was provided invalid value",
            "locations": [
                {
                    "line": 1,
                    "column": 26
                }
            ],
            "extensions": {
                "value": null,
                "problems": [
                    {
                        "path": [],
                        "explanation": "Expected value to not be null"
                    }
                ]
            }
        }
    ]
}

(back to top)

Roadmap

  • Database Schema
  • Set Up Mock Server on Postman (browser version)
    • JSON Contracts
  • Configure gems and GraphQL for creating an API
  • CircleCI and Render CI/CD
  • Set up consumption architecture using Service/Facade/Poro design pattern
  • Query: getMonsters, gets list of all monsters with some attributes used for filter and create page
    • Set up MonsterType class under Types module
    • Set up field for :monster in QueryType class and create monsters method using MonsterFacade
  • Query: getMonster, gets a single monsters with full attributes, by index
    • Add additional fields to MonsterType class under Types module
    • Set up field for :monsters with required argument in QueryType class and create monster(index:) method using MonsterFacade
  • Query: getEncounters, gets list of encounters with attributes, by userName
    • Set up EncounterType class under Types module
    • Set up EncounterMonsterType class under Types module
    • Set up field for :encounters in QueryType class and create encounters(userName:) method using ActiveRecord query
  • Query: getEncounter, gets a single encounter by id
    • Set up field for :encounter with required argument in QueryType class and create encounter(id:) method using ActiveRecord query
  • Mutation: createEncounter, creates new encounter and saves to database using input variables
    • Add field for :create_encounter to MutationType class in Types module
    • Set up CreateEncounter class for mutation, under app/graphql/mutations
      • Add arguments required for encounter creation (encounterMonsterIndexes is an array of index strings)
      • Add fields for return in response upon creation, :encounter and :errors
      • Error handling logic
    • REFACTOR IDEA: Set up a Type for input, EncounterInputType, that can be used fo intake input as a single object variable for further mutations

See the open issues for a full list of proposed features (and known issues).

(back to top)

Contact

Organization: Tavern Keeper

Organization Link: https://github.com/Tavern-Keeper-2308

Project Link: https://github.com/Tavern-Keeper-2308/tavern_keeper_be

(back to top)

Contributors

Xander Hendry

LinkedIn GitHub

Sam Tran

LinkedIn GitHub

Kevin Zolman

LinkedIn GitHub

Erica Hagle

LinkedIn GitHub

Arden Ranta

LinkedIn GitHub

Releases

No releases published

Packages

No packages published

Languages

  • Ruby 97.1%
  • Dockerfile 2.0%
  • Other 0.9%