From ec49b7c873e52fc9fe04f7b1ec1a08bf7980d7ad Mon Sep 17 00:00:00 2001 From: Joey Hines Date: Sun, 16 Aug 2020 12:06:51 -0500 Subject: [PATCH 1/3] First pass of a GraphQL API + All location types can be queried based on name or id + If no query parameters are provided, all locations are returned + Integrated with the api tokens for the other APIs --- GeoffreyApp/api/models.py | 1 - GeoffreyApp/api/schema.py | 189 ++++++++++++++++++++++++++++++++++++++ setup.py | 2 +- 3 files changed, 190 insertions(+), 2 deletions(-) delete mode 100644 GeoffreyApp/api/models.py create mode 100644 GeoffreyApp/api/schema.py diff --git a/GeoffreyApp/api/models.py b/GeoffreyApp/api/models.py deleted file mode 100644 index 137941f..0000000 --- a/GeoffreyApp/api/models.py +++ /dev/null @@ -1 +0,0 @@ -from django.db import models diff --git a/GeoffreyApp/api/schema.py b/GeoffreyApp/api/schema.py new file mode 100644 index 0000000..93150a7 --- /dev/null +++ b/GeoffreyApp/api/schema.py @@ -0,0 +1,189 @@ +import graphene +from django.http import JsonResponse +from graphene_django import DjangoObjectType + +from GeoffreyApp.models import * +from GeoffreyApp.api.key import check_key + + +class UnauthorizedQuery(Exception): + """ + Unauthorized Query + """ + + def __init__(self): + super().__init__("GraphQL Request Not Authorized") + +class PlayerType(DjangoObjectType): + class Meta: + model = Player + fields = ("id", "name", "discord_uuid", "mc_uuid") + + +class TunnelType(DjangoObjectType): + class Meta: + model = Tunnel + fields = ("tunnel_number", "tunnel_direction") + + +class ItemListingType(DjangoObjectType): + class Meta: + model = ItemListing + fields = ("item_name", "price", "amount", "date_restocked", "normalized_price", "id") + + normalized_price = graphene.Float() + + +class ResourceType(DjangoObjectType): + class Meta: + model = Resource + fields = ("resource_name", "id") + + +class LocationType(DjangoObjectType): + class Meta: + model = Location + fields = ("id", "name", "loc_type", "x_coord", "z_coord", "dimension", "owner", "tunnel", "link") + + loc_type = graphene.String() + owner = graphene.List(PlayerType) + link = graphene.String() + tunnel = graphene.Field(TunnelType) + + def resolve_owner(self, info): + return self.owner.all() + + +class ShopType(LocationType): + class Meta: + model = Shop + + inventory = graphene.List(ItemListingType) + + def resolve_inventory(self, info): + return self.shop_selling.all() + + +class PublicFarmType(LocationType): + class Meta: + model = PublicFarm + + resource = graphene.List(ResourceType) + + def resolve_resource(self, info): + return self.resource.all() + + +class BaseType(LocationType): + class Meta: + model = Base + + +class PointOfInterestType(LocationType): + class Meta: + model = PointOfInterest + + +class MarketType(LocationType): + class Meta: + model = Market + + shops = graphene.List(ShopType) + + def resolve_shops(self, info): + return self.get_shops() + + +class AttractionType(LocationType): + class Meta: + model = Attraction + + +class TownType(LocationType): + class Meta: + model = Attraction + + residents = graphene.List(PlayerType) + + def resolve_residents(self, info): + return self.residents.all() + + +class ProtectedAPI(): + def __init__(self, func): + self.func = func + + @staticmethod + def protected_api(): + def wrapper(func): + return ProtectedAPI(func) + + return wrapper + + def __call__(self, root, info, **kwargs): + if "X-GeoffreyAPI-Token" in info.context.headers: + token = info.context.headers["X-GeoffreyAPI-Token"] + elif "token" in info.context.GET: + token = info.context.GET["token"] + else: + raise UnauthorizedQuery + + if check_key(token, model_api_perm=True): + return self.func(root, info, **kwargs) + else: + return UnauthorizedQuery + + +class Query(graphene.ObjectType): + player = graphene.Field(PlayerType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False), discord_uuid=graphene.Argument(graphene.String, required=False)) + locations = graphene.List(LocationType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False)) + shops = graphene.List(ShopType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False)) + towns = graphene.List(TownType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False)) + farms = graphene.List(PublicFarmType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False)) + bases = graphene.List(BaseType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False)) + attractions = graphene.List(AttractionType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False)) + points_of_interest = graphene.List(PointOfInterestType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False)) + + @ProtectedAPI.protected_api() + def resolve_player(root, info, id, name, discord_uuid, mc_uuid): + return Player.objects.get(pk=id, name=name, discord_uuid=discord_uuid, mc_uuid=mc_uuid) + + @ProtectedAPI.protected_api() + def resolve_locations(root, info, id=None, name=None): + return get_location(root, info, id=id, name=name, location_type=Location) + + @ProtectedAPI.protected_api() + def resolve_shops(root, info, id=None, name=None): + return get_location(root, info, id=id, name=name, location_type=Shop) + + @ProtectedAPI.protected_api() + def resolved_towns(root, info, id=None, name=None): + return get_location(root, info, id=id, name=name, location_type=Town) + + @ProtectedAPI.protected_api() + def resolved_farms(root, info, id=None, name=None): + return get_location(root, info, id=id, name=name, location_type=PublicFarm) + + @ProtectedAPI.protected_api() + def resolved_bases(root, info, id=None, name=None): + return get_location(root, info, id=id, name=name, location_type=Base) + + @ProtectedAPI.protected_api() + def resolved_attractions(root, info, id=None, name=None): + return get_location(root, info, id=id, name=name, location_type=Attraction) + + @ProtectedAPI.protected_api() + def resolved_points_of_interest(root, info, id=None, name=None): + return get_location(root, info, id=id, name=name, location_type=PointOfInterest) + + +def get_location(root, info, id=None, name=None, location_type=Location): + if id is not None: + return location_type.objects.get(pk=id) + elif name is not None: + return location_type.objects.get(name__icontains=name) + else: + return location_type.objects.all() + + +schema = graphene.Schema(query=Query) diff --git a/setup.py b/setup.py index d578770..9c86192 100644 --- a/setup.py +++ b/setup.py @@ -18,5 +18,5 @@ setup( url='https:/geoffrey.zerohighdef.com/', author='ZeroHD', author_email='zero@zerohighdef.com', - install_requires=['django', 'requests', 'simplejson'] + install_requires=['django', 'requests', 'simplejson', 'graphene_django'] ) From 2f846de921b7ff7e009c163230db4e1909bcbcb5 Mon Sep 17 00:00:00 2001 From: Joey Hines Date: Sat, 22 Aug 2020 13:29:49 -0500 Subject: [PATCH 2/3] Added a way to set a primary location + Can be retrieved from the GraphQL API + Fixed `resolve_player` query --- GeoffreyApp/api/commands.py | 20 ++++++++++++++++++++ GeoffreyApp/api/schema.py | 22 ++++++++++++++++++---- GeoffreyApp/models.py | 12 ++++++++++-- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/GeoffreyApp/api/commands.py b/GeoffreyApp/api/commands.py index 0249177..eb29239 100644 --- a/GeoffreyApp/api/commands.py +++ b/GeoffreyApp/api/commands.py @@ -853,3 +853,23 @@ def mod_remove_owner(player_name, loc_name): location = get_location(owner, loc_name) location.owner.remove(owner) + + +@command(RequestTypes.POST, permission_level=PermissionLevel.PLAYER) +def primary_location(loc_name, discord_uuid=None, mc_uuid=None): + """ + :request: POST + :param loc_name: location to set as primary + :param discord_uuid: player discord uuid + :param mc_uuid: player mc uuid + :return: json representation of the primary location + """ + player = get_player(discord_uuid, mc_uuid) + loc = get_location(player, loc_name) + + player.primary_location = loc + + player.save() + + return loc.json + diff --git a/GeoffreyApp/api/schema.py b/GeoffreyApp/api/schema.py index 93150a7..a8fa165 100644 --- a/GeoffreyApp/api/schema.py +++ b/GeoffreyApp/api/schema.py @@ -14,10 +14,13 @@ class UnauthorizedQuery(Exception): def __init__(self): super().__init__("GraphQL Request Not Authorized") + class PlayerType(DjangoObjectType): class Meta: model = Player - fields = ("id", "name", "discord_uuid", "mc_uuid") + fields = ("id", "name", "discord_uuid", "mc_uuid", "primary_location") + + primary_location = graphene.Field("GeoffreyApp.api.schema.LocationType") class TunnelType(DjangoObjectType): @@ -135,7 +138,7 @@ class ProtectedAPI(): class Query(graphene.ObjectType): - player = graphene.Field(PlayerType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False), discord_uuid=graphene.Argument(graphene.String, required=False)) + player = graphene.Field(PlayerType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False), mc_uuid=graphene.Argument(graphene.String, required=False), discord_uuid=graphene.Argument(graphene.String, required=False)) locations = graphene.List(LocationType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False)) shops = graphene.List(ShopType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False)) towns = graphene.List(TownType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False)) @@ -145,8 +148,19 @@ class Query(graphene.ObjectType): points_of_interest = graphene.List(PointOfInterestType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False)) @ProtectedAPI.protected_api() - def resolve_player(root, info, id, name, discord_uuid, mc_uuid): - return Player.objects.get(pk=id, name=name, discord_uuid=discord_uuid, mc_uuid=mc_uuid) + def resolve_player(root, info, id=None, name=None, discord_uuid=None, mc_uuid=None): + query = Player.objects + if id is not None: + query = query.filter(id=id) + elif name is not None: + query = query.filter(name__iexact=name) + elif discord_uuid is not None: + query = query.filter(discord_uuid=discord_uuid) + elif mc_uuid is not None: + query = query.filter(mc_uuid=mc_uuid) + + return query.get() + @ProtectedAPI.protected_api() def resolve_locations(root, info, id=None, name=None): diff --git a/GeoffreyApp/models.py b/GeoffreyApp/models.py index 9931801..8fd3490 100644 --- a/GeoffreyApp/models.py +++ b/GeoffreyApp/models.py @@ -83,6 +83,11 @@ class Player(models.Model): Discord UUID """ + primary_location = models.OneToOneField("Location", on_delete=models.DO_NOTHING, null=True) + """ + User's primary location + """ + @property def loc_count(self): """ @@ -101,12 +106,14 @@ class Player(models.Model): "name" : "self.name", "mc_uuid": "self.mc_uuid", "discord_uuid": "self.discord_uuid", + "primary_location": "self.primary_location" } """ return {"name": self.name, "mc_uuid": self.mc_uuid, - "discord_uuid": self.discord_uuid + "discord_uuid": self.discord_uuid, + "primary_location": self.primary_location.json } @property @@ -218,7 +225,8 @@ class Location(models.Model): "owner": self.get_owners, "location": self.position, "tunnel": None if self.tunnel is None else self.tunnel.tunnel_str, - "link": self.link + "link": self.link, + "primary_location": self.player.primary_location } @property From 9eabf09fc0bbb92b9afb12af1dc26977894bca92 Mon Sep 17 00:00:00 2001 From: Joey Hines Date: Sat, 22 Aug 2020 13:39:25 -0500 Subject: [PATCH 3/3] Added item_listing query + items can be searched or all items can be returned + Added missing migration --- GeoffreyApp/api/schema.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/GeoffreyApp/api/schema.py b/GeoffreyApp/api/schema.py index a8fa165..be7fc38 100644 --- a/GeoffreyApp/api/schema.py +++ b/GeoffreyApp/api/schema.py @@ -35,6 +35,10 @@ class ItemListingType(DjangoObjectType): fields = ("item_name", "price", "amount", "date_restocked", "normalized_price", "id") normalized_price = graphene.Float() + shop = graphene.Field("GeoffreyApp.api.schema.ShopType") + + def resolve_shop(self, info): + return self.shop class ResourceType(DjangoObjectType): @@ -146,6 +150,7 @@ class Query(graphene.ObjectType): bases = graphene.List(BaseType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False)) attractions = graphene.List(AttractionType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False)) points_of_interest = graphene.List(PointOfInterestType, id=graphene.Argument(graphene.Int, required=False), name=graphene.Argument(graphene.String, required=False)) + item_listing = graphene.List(ItemListingType, item=graphene.Argument(graphene.String, required=False)) @ProtectedAPI.protected_api() def resolve_player(root, info, id=None, name=None, discord_uuid=None, mc_uuid=None): @@ -161,7 +166,6 @@ class Query(graphene.ObjectType): return query.get() - @ProtectedAPI.protected_api() def resolve_locations(root, info, id=None, name=None): return get_location(root, info, id=id, name=name, location_type=Location) @@ -190,6 +194,14 @@ class Query(graphene.ObjectType): def resolved_points_of_interest(root, info, id=None, name=None): return get_location(root, info, id=id, name=name, location_type=PointOfInterest) + @ProtectedAPI.protected_api() + def resolve_item_listing(root, info, item=None): + query = ItemListing.objects + if item is not None: + query = query.filter(item_name=item) + + return query.all() + def get_location(root, info, id=None, name=None, location_type=Location): if id is not None: