diff --git a/.github/workflows/deploy-activity-tracking.yml b/.github/workflows/deploy-activity-tracking.yml new file mode 100644 index 00000000..88e82f32 --- /dev/null +++ b/.github/workflows/deploy-activity-tracking.yml @@ -0,0 +1,38 @@ +name: Deploy to AWS ECR + +on: + push: + branches: + - main + paths: + - "activity-tracking/**" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: | + echo The PR was merged + - name: Checkout code + uses: actions/checkout@v2 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: eu-west-2 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR (Activity Tracking) + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: mla-fitnessapp-activity + IMAGE_TAG: latest + run: | + cd activity-tracking + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfile . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG diff --git a/.github/workflows/deploy-analytics.yml b/.github/workflows/deploy-analytics.yml new file mode 100644 index 00000000..bd7b5625 --- /dev/null +++ b/.github/workflows/deploy-analytics.yml @@ -0,0 +1,38 @@ +name: Deploy to AWS ECR + +on: + push: + branches: + - main + paths: + - "analytics/**" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: | + echo The PR was merged + - name: Checkout code + uses: actions/checkout@v2 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: eu-west-2 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR (Analytics) + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: mla-fitnessapp-analytics + IMAGE_TAG: latest + run: | + cd analytics + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfile . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG diff --git a/.github/workflows/deploy-auth.yml b/.github/workflows/deploy-auth.yml new file mode 100644 index 00000000..1f426d5a --- /dev/null +++ b/.github/workflows/deploy-auth.yml @@ -0,0 +1,38 @@ +name: Deploy to AWS ECR + +on: + push: + branches: + - main + paths: + - "authservice/**" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: | + echo The PR was merged + - name: Checkout code + uses: actions/checkout@v2 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: eu-west-2 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR (AuthService) + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: mla-fitnessapp-authservice + IMAGE_TAG: latest + run: | + cd authservice + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfile . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG diff --git a/.github/workflows/deploy-frontend.yml b/.github/workflows/deploy-frontend.yml new file mode 100644 index 00000000..2fc8c61c --- /dev/null +++ b/.github/workflows/deploy-frontend.yml @@ -0,0 +1,38 @@ +name: Deploy to AWS ECR + +on: + push: + branches: + - main + paths: + - "frontend/**" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: | + echo The PR was merged + - name: Checkout code + uses: actions/checkout@v2 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: eu-west-2 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR (Frontend) + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: mla-fitnessapp-fe + IMAGE_TAG: latest + run: | + cd frontend + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfile . + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG diff --git a/analytics/app.py b/analytics/app.py index cdd57579..3a6bacb6 100644 --- a/analytics/app.py +++ b/analytics/app.py @@ -9,18 +9,78 @@ import logging import os from datetime import datetime, timedelta +from ariadne import load_schema_from_path, make_executable_schema, graphql_sync, ObjectType, QueryType +from ariadne.constants import PLAYGROUND_HTML app = Flask(__name__) CORS(app, resources={r"/*": {"origins": "*"}}, methods="GET,HEAD,POST,OPTIONS,PUT,PATCH,DELETE") load_dotenv() +title = "Weekly Exercise Tracker Statistics" +heading = "MLA Flask Microservice" +user = "testuser" mongo_uri = os.getenv('MONGO_URI') mongo_db = os.getenv('MONGO_DB') client = MongoClient(mongo_uri) db = client[mongo_db] +query = QueryType() +type_defs = load_schema_from_path("schema.graphql") + +@app.route('/api/graphql', methods=['GET']) +def graphql_playground(): + print("Received a get request") + return PLAYGROUND_HTML, 200 + +@app.route('/api/graphql', methods=['POST']) +def graphql_server(): + print("Getting a request...") + data = request.get_json() + success, result = graphql_sync( + schema, + data, + context_value=request, + debug=True + ) + status_code = 200 if success else 400 + return jsonify(result), status_code + +@query.field("stats") +def resolve_stats(_, info): + try: + print("Resolving the list stats info") + loadedStats = stats() + print(loadedStats) + payload = { + "success": True, + "results": loadedStats + } + except Exception as error: + payload = { + "success": False, + "errors": [str(error)] + } + return payload + +@query.field("filteredStats") +def resolve_filteredStats(*_, name=None): + try: + print("Resolving the list stats info") + loadedStats = user_stats(name) + print(loadedStats) + payload = { + "success": True, + "results": loadedStats + } + except Exception as error: + payload = { + "success": False, + "errors": [str(error)] + } + return payload + @app.route('/') def index(): @@ -103,6 +163,7 @@ def user_stats(username): stats = list(db.exercises.aggregate(pipeline)) return jsonify(stats=stats) +schema = make_executable_schema(type_defs, query) @app.route('/stats/weekly/', methods=['GET']) def weekly_user_stats(): @@ -158,4 +219,4 @@ def weekly_user_stats(): if __name__ == "__main__": - app.run(debug=True, host='0.0.0.0', port=5050) \ No newline at end of file + app.run(debug=True, host='0.0.0.0', port=5050) diff --git a/analytics/requirements.txt b/analytics/requirements.txt index 73ae4171..3c49192c 100644 --- a/analytics/requirements.txt +++ b/analytics/requirements.txt @@ -3,4 +3,25 @@ pymongo==4.2.0 gunicorn==20.1.0 python-dotenv==0.19.0 Flask-PyMongo==2.3.0 -Flask-CORS==3.0.10 \ No newline at end of file +Flask-CORS==3.0.10 +mongomock==3.22.0 +anyio==4.2.0 +ariadne==0.11.0 +blinker==1.7.0 +click==8.1.7 +dnspython==2.4.2 +Flask-SQLAlchemy==3.1.1 +graphql-core==3.0.5 +idna==3.6 +itsdangerous==2.1.2 +Jinja2==3.1.2 +MarkupSafe==2.1.3 +packaging==23.2 +sniffio==1.3.0 +SQLAlchemy==2.0.25 +starlette<0.14 +typing_extensions==4.9.0 +Werkzeug==3.0.1 + + + diff --git a/analytics/schema.graphql b/analytics/schema.graphql new file mode 100644 index 00000000..04dd77a7 --- /dev/null +++ b/analytics/schema.graphql @@ -0,0 +1,23 @@ +schema { + query: Query +} +type Stats{ + username: String! + exercises: [Exercise] +} + +type Exercise { + exerciseType: String + totalDuration: Int +} + +type StatsResult { + success: Boolean! + errors: [String] + results: [Stats] +} + +type Query { + stats: StatsResult + filteredStats(name: String): StatsResult +} diff --git a/analytics/test_api.py b/analytics/test_api.py new file mode 100644 index 00000000..30485ff3 --- /dev/null +++ b/analytics/test_api.py @@ -0,0 +1,47 @@ +# test_analytics_api.py +import unittest +from flask import json +from app import app +import mongomock + +class AnalyticsAPITestCase(unittest.TestCase): + def setUp(self): + # Create a mongomock client + self.mock_client = mongomock.MongoClient() + app.config["MONGO_URI"] = "mongomock://localhost" + + # Patch the application's database client with the mock client + app.db_client = self.mock_client + + # Continue with setting up a test client for Flask + self.app = app.test_client() + #test_client used to simulate requests and test reponse without running server + self.app.testing = True + + def test_root_endpoint(self): + response = self.app.get('/') + self.assertEqual(response.status_code, 200) + # Assuming the response is JSON with a list of exercises + data = json.loads(response.data) + self.assertIsInstance(data, list) # Check if data is a list + + def test_stats_endpoint(self): + response = self.app.get('/stats') + self.assertEqual(response.status_code, 200) + data = json.loads(response.data) + self.assertTrue('stats' in data) # Check if 'stats' key exists in response + + def test_user_stats_endpoint(self): + response = self.app.get('/stats/testuser') + self.assertEqual(response.status_code, 200) + data = json.loads(response.data) + self.assertTrue('stats' in data) + + def test_weekly_user_stats(self): + response = self.app.get('/stats/weekly/?user=testuser&start=2022-01-01&end=2022-01-07') + self.assertEqual(response.status_code, 200) + data = json.loads(response.data) + self.assertTrue('stats' in data) + +if __name__ == '__main__': + unittest.main()