diff --git a/lib/pbench/server/api/__init__.py b/lib/pbench/server/api/__init__.py index 3fb8d0139f..19fecf4cf1 100644 --- a/lib/pbench/server/api/__init__.py +++ b/lib/pbench/server/api/__init__.py @@ -6,14 +6,14 @@ import os -from flask import Flask +from flask import Flask, request from flask_restful import Api from flask_cors import CORS from pbench.server import PbenchServerConfig from pbench.common.exceptions import BadConfig, ConfigFileNotSpecified from pbench.server.api.resources.upload_api import Upload, HostInfo -from pbench.server.api.resources.graphql_api import GraphQL +from pbench.server.api.resources.graphql_api import GraphQL, UserMetadata from pbench.common.logger import get_pbench_logger from pbench.server.api.resources.query_apis.elasticsearch_api import Elasticsearch from pbench.server.api.resources.query_apis.query_controllers import QueryControllers @@ -74,6 +74,10 @@ def register_endpoints(api, app, config): UserAPI, f"{base_uri}/user/", resource_class_args=(logger,), ) + api.add_resource( + UserMetadata, f"{base_uri}/user/metadata", resource_class_args=(config, logger), + ) + def get_server_config(): cfg_name = os.environ.get("_PBENCH_SERVER_CONFIG") @@ -107,6 +111,11 @@ def create_app(server_config): Database.init_db(server_config=server_config, logger=app.logger) + @app.before_request + def before_request(): + print(request.path) + print(request.remote_addr) + @app.teardown_appcontext def shutdown_session(exception=None): Database.db_session.remove() diff --git a/lib/pbench/server/api/resources/graphql_api.py b/lib/pbench/server/api/resources/graphql_api.py index 2f6e41a861..343a363df5 100644 --- a/lib/pbench/server/api/resources/graphql_api.py +++ b/lib/pbench/server/api/resources/graphql_api.py @@ -1,6 +1,87 @@ import requests from flask_restful import Resource, abort -from flask import request, make_response +from flask import request, make_response, jsonify +from pbench.server.api.resources.auth import auth +from pbench.server.api.resources.graphql_schema import schema + + +class UserMetadata(Resource): + """ + Abstracted pbench API for handling user metadata by using graphql schema + """ + + def __init__(self, config, logger): + self.server_config = config + self.logger = logger + + @auth.login_required() + def post(self): + """ + Post request for creating metadata instance for a user. + This requires a JWT auth token in the header field + + This requires a JSON data with required user metadata fields + { + "config": "config", + "description": "description", + } + + Required headers include + Authorization: JWT token (user received upon login) + + :return: JSON Payload + response_object = { + "status": "success" + "data" { + "id": "metadata_id", + "config": "Config string" + "description": "Description string" + } + } + """ + post_data = request.get_json() + if not post_data: + self.logger.warning("Invalid json object: %s", request.url) + abort(400, message="Invalid json object in request") + + config = post_data.get("config") + if not config: + self.logger.warning("Config not provided during metadata creation") + abort(400, message="Please provide a config string") + + description = post_data.get("description") + if not description: + self.logger.warning("Description not provided during metadata creation") + abort(400, message="Please provide a description string") + current_user_id = auth.current_user().id + try: + # query GraphQL + query = f""" + mutation {{ + createMetadata (input: {{config:"{config}", description:"{description}", user_id:{current_user_id}}}) {{ + metadata {{ + id + config + description + }} + }} + }} + """ + result = schema.execute(query) + except Exception as e: + self.logger.exception("Exception occurred during Metadata creation") + abort(500, message="INTERNAL ERROR") + else: + data = result.data["createMetadata"]["metadata"] + response_object = { + "status": "success", + "data": { + "id": data["id"], + "config": data["config"], + "description": data["description"], + }, + } + return make_response(jsonify(response_object), 201) class GraphQL(Resource): diff --git a/lib/pbench/server/api/resources/graphql_schema.py b/lib/pbench/server/api/resources/graphql_schema.py new file mode 100644 index 0000000000..f8fbe0c6eb --- /dev/null +++ b/lib/pbench/server/api/resources/graphql_schema.py @@ -0,0 +1,74 @@ +from pbench.server.api.resources.models import MetadataModel +from pbench.server.api.resources.database import Database + +import graphene +from graphene import relay +from graphene_sqlalchemy import SQLAlchemyObjectType + + +# Define graphql types +class Metadata(SQLAlchemyObjectType): + class Meta: + model = MetadataModel + interfaces = (relay.Node,) + + +class MetadataAttribute: + id = graphene.ID + user_id = graphene.ID() + created = graphene.DateTime() + updated = graphene.DateTime() + config = graphene.String() + description = graphene.String() + + +class CreateMetadataInput(graphene.InputObjectType, MetadataAttribute): + pass + + +# mutations +class CreateMetadata(graphene.Mutation): + metadata = graphene.Field(lambda: Metadata) + ok = graphene.Boolean() + + class Arguments: + input = CreateMetadataInput(required=True) + + @staticmethod + def mutate(self, info, input): + data = input + metadata = MetadataModel(**data) + Database.db_session.add(metadata) + Database.db_session.commit() + ok = True + return CreateMetadata(metadata=metadata, ok=ok) + + +class Mutation(graphene.ObjectType): + createMetadata = CreateMetadata.Field() + + +# Query +class Query(graphene.ObjectType): + node = relay.Node.Field() + + metadata_by_id = graphene.List(Metadata, id=graphene.String()) + metadata_by_userid = graphene.List(Metadata, userid=graphene.String()) + + @staticmethod + def resolve_metadata_by_id(parent, info, **args): + q = args.get("id") + + metadata_query = Metadata.get_query(info) + return metadata_query.filter(MetadataModel.id == q).all() + + @staticmethod + def resolve_metadata_by_userid(parent, info, **args): + q = args.get("userid") + + metadata_query = Metadata.get_query(info) + return metadata_query.filter(MetadataModel.user_id == q).all() + + +# schema +schema = graphene.Schema(query=Query, mutation=Mutation, types=[Metadata]) diff --git a/lib/pbench/server/api/resources/models.py b/lib/pbench/server/api/resources/models.py index d0a14c9965..3b727c8739 100644 --- a/lib/pbench/server/api/resources/models.py +++ b/lib/pbench/server/api/resources/models.py @@ -19,6 +19,7 @@ class UserModel(Database.Base): registered_on = Column(DateTime, nullable=False) email = Column(String(255), unique=True, nullable=False) auth_tokens = relationship("ActiveTokens", backref="users") + user_metadata = relationship("MetadataModel", uselist=False, backref="users") def __init__(self, bcrypt_log_rounds, **kwargs): super().__init__(**kwargs) @@ -76,7 +77,7 @@ def valid(auth_token): return False -class Metadata(Database.Base): +class MetadataModel(Database.Base): """ Metadata Model for storing user metadata details """ # TODO: Think about the better name @@ -87,12 +88,14 @@ class Metadata(Database.Base): updated = Column(DateTime, nullable=False) config = Column(String(255), unique=False, nullable=False) description = Column(String(255), nullable=False) + user_id = Column(Integer, ForeignKey('users.id')) - def __init__(self, created, config, description): + def __init__(self, created, config, description, user_id): self.created = parser.parse(created) self.updated = datetime.datetime.now() self.config = config self.description = description + self.user_id = user_id def __str__(self): return f"Url id: {self.id}, created on: {self.created}, description: {self.description}"