From fc6efda0c2101db46998695b85cfb474ce186fed Mon Sep 17 00:00:00 2001
From: Safgerd <safgerdfeed@gmail.com>
Date: Sun, 17 Nov 2024 08:07:00 +0400
Subject: [PATCH] minhasapov_ruslan_lab_3

---
 minhasapov_ruslan_lab_3/README.md             |  40 ++++++
 .../club_service/Dockerfile                   |  11 ++
 .../club_service/club_service.py              | 103 +++++++++++++++
 minhasapov_ruslan_lab_3/docker-compose.yaml   |  26 ++++
 minhasapov_ruslan_lab_3/nginx.conf            |  25 ++++
 minhasapov_ruslan_lab_3/requirements.txt      |   2 +
 .../resource_service/Dockerfile               |  11 ++
 .../resource_service/resource_service.py      | 118 ++++++++++++++++++
 8 files changed, 336 insertions(+)
 create mode 100644 minhasapov_ruslan_lab_3/README.md
 create mode 100644 minhasapov_ruslan_lab_3/club_service/Dockerfile
 create mode 100644 minhasapov_ruslan_lab_3/club_service/club_service.py
 create mode 100644 minhasapov_ruslan_lab_3/docker-compose.yaml
 create mode 100644 minhasapov_ruslan_lab_3/nginx.conf
 create mode 100644 minhasapov_ruslan_lab_3/requirements.txt
 create mode 100644 minhasapov_ruslan_lab_3/resource_service/Dockerfile
 create mode 100644 minhasapov_ruslan_lab_3/resource_service/resource_service.py

diff --git a/minhasapov_ruslan_lab_3/README.md b/minhasapov_ruslan_lab_3/README.md
new file mode 100644
index 0000000..78b473a
--- /dev/null
+++ b/minhasapov_ruslan_lab_3/README.md
@@ -0,0 +1,40 @@
+# Лабораторная работа №3
+#### ПИбд-42. Минхасапов Руслан.
+
+---
+
+#### При выполнении лабораторной работы были использованы:
+- Python 3.12
+- Flask
+- requests
+- Docker
+- Docker Compose
+
+---
+
+#### Задание:
+В рамках данной работы были созданы сущности: 
+##### 1. Клуб. Имеет поля: 
+    - id
+    - address
+    - phone
+#####  2. Ресурс. Имеет поля: 
+    - id
+    - name
+    - amount
+    - club_id
+
+##### Каждому клубу могут принадлежать много ресурсов.
+##### Были развернуты два сервиса - club_service и resource_service, синхронно обменивающиеся сообщениями. 
+##### Сущности хранятся в оперативной памяти (без БД)
+
+---
+
+#### Инструкция
+Для запуска выполненной лабораторной работы необходимо перейти в директорию *minhasapov_ruslan_lab_3* и выполнить команду в терминале:
+```
+docker-compose up --build -d --remove-orphans
+```
+
+#### Демонстрация работы
+Доступна по [ссылке](https://disk.yandex.ru/i/IeNyzBXG1oDSWg)
\ No newline at end of file
diff --git a/minhasapov_ruslan_lab_3/club_service/Dockerfile b/minhasapov_ruslan_lab_3/club_service/Dockerfile
new file mode 100644
index 0000000..bc10eb8
--- /dev/null
+++ b/minhasapov_ruslan_lab_3/club_service/Dockerfile
@@ -0,0 +1,11 @@
+FROM python:latest
+
+WORKDIR /app
+
+COPY requirements.txt .
+
+RUN pip install --no-cache-dir -r requirements.txt
+
+COPY club_service/club_service.py .
+
+CMD ["python", "club_service.py"]
\ No newline at end of file
diff --git a/minhasapov_ruslan_lab_3/club_service/club_service.py b/minhasapov_ruslan_lab_3/club_service/club_service.py
new file mode 100644
index 0000000..e86ae06
--- /dev/null
+++ b/minhasapov_ruslan_lab_3/club_service/club_service.py
@@ -0,0 +1,103 @@
+from flask import Flask, jsonify, request
+import requests
+
+app = Flask(__name__)
+
+resources_url = 'http://resourceService:20002/'
+
+class Club:
+    def __init__(self, id: int, address: str, phone: str):
+        self.id = id
+        self.address = address
+        self.phone = phone
+
+    def to_dict(self):
+        return {
+            "id": self.id,
+            "address": self.address,
+            "phone": self.phone
+        }
+
+    def to_dict_with_resources(self, resources: list):
+        return{
+            "id": self.id,
+            "address": self.address,
+            "phone": self.phone,
+            "resources": resources
+        }
+
+clubs = [
+    Club(1, 'Пушкина 3', '88005553535'),
+    Club(2, 'Колотушкина 4', '89006664646')
+]
+
+@app.route('/', methods=['GET'])
+def get_all_clubs():
+    return jsonify([club.to_dict() for club in clubs]), 200
+
+@app.route('/<int:club_id>', methods=['GET'])
+def get_club(club_id):
+    for club in clubs:
+        if club.id == club_id:
+            return club.to_dict(), 200
+        
+    return f'Club with id {club_id} not found', 404
+
+@app.route('/info/<int:club_id>', methods=['GET'])
+def get_club_with_resources(club_id):
+    for club in clubs:
+        if club.id == club_id:
+            response = requests.get(resources_url + f'by-club/{club_id}')
+
+            return club.to_dict_with_resources(response.json()), 200
+    
+    return f'Club with id {club_id} not found', 404
+
+@app.route('/check/<int:club_id>', methods=['GET']) 
+def check_club_exists(club_id):
+    for club in clubs:
+        if club.id == club_id:  
+            return '', 200
+    
+    return '', 404
+
+@app.route('/', methods=['POST'])
+def create_club():
+    data = request.json
+    address = data.get('address')
+    phone = data.get('phone')
+
+    if address is None or phone is None:
+        return 'Address and phone required to create club', 400
+    
+    next_id = max([c.id for c in clubs]) + 1 if clubs else 1
+    new_club = Club(next_id, address, phone)
+    clubs.append(new_club)
+
+    return jsonify(new_club.to_dict()), 201
+
+@app.route('/<int:club_id>', methods=['PUT'])  
+def update_club(club_id):
+    data = request.json
+
+    for club in clubs:
+        if club.id == club_id: 
+            club.address = data.get('address', club.address)
+            club.phone = data.get('phone', club.phone)
+
+            return jsonify(club.to_dict()), 200
+    
+    return f'Club with id {club_id} not found', 404 
+
+@app.route('/<int:club_id>', methods=['DELETE'])  
+def delete_club(club_id):
+    for club in clubs:
+        if club.id == club_id:  
+            clubs.remove(club)
+
+            return 'Club deleted', 200
+    
+    return f'Club with id {club_id} not found', 404 
+
+if __name__ == '__main__':
+    app.run(host='0.0.0.0', port=20001, debug=True)
\ No newline at end of file
diff --git a/minhasapov_ruslan_lab_3/docker-compose.yaml b/minhasapov_ruslan_lab_3/docker-compose.yaml
new file mode 100644
index 0000000..d0035df
--- /dev/null
+++ b/minhasapov_ruslan_lab_3/docker-compose.yaml
@@ -0,0 +1,26 @@
+services:
+  club_service:
+    container_name: clubService
+    build:
+      context: .
+      dockerfile: ./club_service/Dockerfile
+    expose:
+      - 20001
+
+  resource_service:
+    container_name: resourceService
+    build:
+      context: .
+      dockerfile: ./resource_service/Dockerfile
+    expose:
+      - 20002
+
+  nginx:
+    image: nginx:latest
+    ports:
+      - "80:80"
+    volumes:
+      - ./nginx.conf:/etc/nginx/nginx.conf
+    depends_on:
+      - club_service
+      - resource_service
\ No newline at end of file
diff --git a/minhasapov_ruslan_lab_3/nginx.conf b/minhasapov_ruslan_lab_3/nginx.conf
new file mode 100644
index 0000000..6c3c225
--- /dev/null
+++ b/minhasapov_ruslan_lab_3/nginx.conf
@@ -0,0 +1,25 @@
+events { worker_connections 1024; }
+
+http {
+    server {
+        listen      80;
+        listen      [::]:80;
+        server_name localhost;
+
+        location /clubService/ {
+            proxy_pass http://clubService:20001/;
+            proxy_set_header Host $host;
+            proxy_set_header X-Real-IP $remote_addr;
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto $scheme;
+        }
+
+        location /resourceService/ {
+            proxy_pass http://resourceService:20002/;
+            proxy_set_header Host $host;
+            proxy_set_header X-Real-IP $remote_addr;
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto $scheme;
+        }
+    }
+}
\ No newline at end of file
diff --git a/minhasapov_ruslan_lab_3/requirements.txt b/minhasapov_ruslan_lab_3/requirements.txt
new file mode 100644
index 0000000..494909e
--- /dev/null
+++ b/minhasapov_ruslan_lab_3/requirements.txt
@@ -0,0 +1,2 @@
+Flask==3.0.3
+requests==2.32.3
\ No newline at end of file
diff --git a/minhasapov_ruslan_lab_3/resource_service/Dockerfile b/minhasapov_ruslan_lab_3/resource_service/Dockerfile
new file mode 100644
index 0000000..e7eac4c
--- /dev/null
+++ b/minhasapov_ruslan_lab_3/resource_service/Dockerfile
@@ -0,0 +1,11 @@
+FROM python:latest
+
+WORKDIR /app
+
+COPY requirements.txt .
+
+RUN pip install --no-cache-dir -r requirements.txt
+
+COPY resource_service/resource_service.py .
+
+CMD ["python", "resource_service.py"]
\ No newline at end of file
diff --git a/minhasapov_ruslan_lab_3/resource_service/resource_service.py b/minhasapov_ruslan_lab_3/resource_service/resource_service.py
new file mode 100644
index 0000000..5911357
--- /dev/null
+++ b/minhasapov_ruslan_lab_3/resource_service/resource_service.py
@@ -0,0 +1,118 @@
+from flask import Flask, jsonify, request
+import requests
+
+app = Flask(__name__)
+
+clubs_url = 'http://clubService:20001/'
+
+class Resource:
+    def __init__(self, id: int, name: str, amount: int, club_id: int):
+        self.id = id
+        self.name = name
+        self.amount = amount
+        self.club_id = club_id
+
+    def to_dict(self):
+        return {
+            "id": self.id,
+            "name": self.name,
+            "amount": self.amount,
+            "club_id": self.club_id
+        }
+    
+    def to_dict_with_club(self, club):
+        return {
+            "id": self.id,
+            "name": self.name,
+            "amount": self.amount,
+            "club": club
+        }
+
+resources = [
+    Resource(1, 'PS5', 3, 1),
+    Resource(2, 'XBOX', 4, 2)
+]
+
+@app.route('/', methods=['GET'])
+def get_all_resources():
+    return jsonify([resource.to_dict() for resource in resources]), 200
+
+@app.route('/by-club/<int:club_id>', methods=['GET']) 
+def get_resources_by_club(club_id):
+    resources_for_club = [resource.to_dict() for resource in resources if resource.club_id == club_id]
+
+    return jsonify(resources_for_club), 200
+
+@app.route('/<int:resource_id>', methods=['GET'])  
+def get_resource(resource_id):
+    for resource in resources:
+        if resource.id == resource_id:  
+            return jsonify(resource.to_dict()), 200
+    
+    return f'Resource with id {resource_id} not found', 404 
+
+@app.route('/info/<int:resource_id>', methods=['GET'])  
+def get_resource_with_club(resource_id):
+    for resource in resources:
+        if resource.id == resource_id: 
+            try:
+                response = requests.get(clubs_url + str(resource.club_id))
+                response.raise_for_status()
+                club = response.json()
+
+                return jsonify(resource.to_dict_with_club(club)), 200
+            
+            except requests.exceptions.RequestException as e:
+                return f"Error fetching club: {e}", 500
+
+    return f'Resource with id {resource_id} not found', 404  
+
+@app.route('/', methods=['POST'])
+def create_resource():
+    data = request.json
+    name = data.get('name')
+    amount = data.get('amount')
+    club_id = data.get('club_id')
+
+    try:
+        response = requests.get(clubs_url + f'check/{club_id}')
+        response.raise_for_status()
+
+        if response.status_code == 200:
+            next_id = max([r.id for r in resources]) + 1 if resources else 1 
+            new_resource = Resource(next_id, name, amount, club_id)  
+            resources.append(new_resource)
+
+            return jsonify(new_resource.to_dict()), 201
+        else:
+            return f"Club with ID {club_id} not found", 404
+    
+    except requests.exceptions.RequestException as e:
+        return f"Error checking club: {e}", 500
+
+@app.route('/<int:resource_id>', methods=['PUT'])  
+def update_resource(resource_id):
+    data = request.json
+
+    for resource in resources:
+        if resource.id == resource_id:  
+            resource.name = data.get('name', resource.name)
+            resource.amount = data.get('amount', resource.amount)
+            resource.club_id = data.get('club_id', resource.club_id)
+
+            return jsonify(resource.to_dict()), 200
+    
+    return f'Resource with id {resource_id} not found', 404  
+
+@app.route('/<int:resource_id>', methods=['DELETE'])  
+def delete_resource(resource_id):
+    for resource in resources:
+        if resource.id == resource_id:  
+            resources.remove(resource)
+
+            return 'Resource deleted', 200
+    
+    return f'Resource with id {resource_id} not found', 404  
+
+if __name__ == '__main__':
+    app.run(host='0.0.0.0', port=20002, debug=True)
\ No newline at end of file